diff --git a/lib/WeBWorK/ContentGenerator.pm b/lib/WeBWorK/ContentGenerator.pm index af2bd9e6e2..5e29544824 100644 --- a/lib/WeBWorK/ContentGenerator.pm +++ b/lib/WeBWorK/ContentGenerator.pm @@ -2,12 +2,12 @@ # WeBWorK Online Homework Delivery System # Copyright © 2000-2012 The WeBWorK Project, http://github.com/openwebwork # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator.pm,v 1.196 2009/06/04 01:33:15 gage Exp $ -# +# # 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 @@ -23,9 +23,9 @@ WeBWorK::ContentGenerator - base class for modules that generate page content. =head1 SYNOPSIS # start with a WeBWorK::Request object: $r - + use WeBWorK::ContentGenerator::SomeSubclass; - + my $cg = WeBWorK::ContentGenerator::SomeSubclass->new($r); my $result = $cg->go(); @@ -59,9 +59,9 @@ use constant MP2 => ( exists $ENV{MOD_PERL_API_VERSION} and $ENV{MOD_PERL_API_VE use Scalar::Util qw(weaken); use HTML::Entities; use HTML::Scrubber; -use WeBWorK::Utils qw(jitar_id_to_seq); +use WeBWorK::Utils qw(jitar_id_to_seq fetchEmailRecipients generateURLs); use WeBWorK::Authen::LTIAdvanced::SubmitGrade; -use Encode; +use Encode; our $TRACE_WARNINGS = 0; # set to 1 to trace channel used by warning message @@ -173,10 +173,10 @@ sub go { if ($ce->{LTIGradeMode} and ref($r->{db}//'') ) { my $grader = WeBWorK::Authen::LTIAdvanced::SubmitGrade->new($r); - + my $post_connection_action = sub { my $grader = shift; - + # catch exceptions generated during the sending process my $result_message = eval { $grader->mass_update() }; if ($@) { @@ -200,9 +200,9 @@ sub go { # check $self->{invalidSet} and react correctly) my $authz = $r->authz; $self->{invalidSet} = $authz->checkSet(); - + my $returnValue = MP2 ? Apache2::Const::OK : Apache::Constants::OK; - + # We only write to the activity log if it has been defined and if # we are in a specific course. The latter check is to prevent attempts # to write to a course log file when viewing the top-level list of @@ -212,26 +212,26 @@ sub go { $r->ce->{courseFiles}->{logs}->{activity_log}); $self->pre_header_initialize(@_) if $self->can("pre_header_initialize"); - + # send a file instead of a normal reply (reply_with_file() sets this field) defined $self->{reply_with_file} and do { return $self->do_reply_with_file($self->{reply_with_file}); }; - + # send a Location: header instead of a normal reply (reply_with_redirect() sets this field) defined $self->{reply_with_redirect} and do { return $self->do_reply_with_redirect($self->{reply_with_redirect}); }; - + my $headerReturn = $self->header(@_); $returnValue = $headerReturn if defined $headerReturn; # FIXME: we won't need noContent after reply_with_redirect() is adopted return $returnValue if $r->header_only or $self->{noContent}; - + $self->initialize() if $self->can("initialize"); - + $self->content(); - + return $returnValue; } @@ -244,7 +244,7 @@ instance. sub r { my ($self) = @_; - + return $self->{r}; } @@ -257,43 +257,43 @@ Handler for reply_with_file(), used by go(). DO NOT CALL THIS METHOD DIRECTLY. sub do_reply_with_file { my ($self, $fileHash) = @_; my $r = $self->r; - + my $type = $fileHash->{type}; my $source = $fileHash->{source}; my $name = $fileHash->{name}; my $delete_after = $fileHash->{delete_after}; - + # if there was a problem, we return here and let go() worry about sending the reply return MP2 ? Apache2::Const::NOT_FOUND : Apache::Constants::NOT_FOUND unless -e $source; return MP2 ? Apache2::Const::FORBIDDEN : Apache::Constants::FORBIDDEN unless -r $source; - + my $fh; if (!MP2) { # open the file now, so we can send the proper error status is we fail open $fh, "<", $source or return Apache::Constants::SERVER_ERROR; } - + # send our custom HTTP header $r->content_type($type); $r->headers_out->{"Content-Disposition"} = "attachment; filename=\"$name\""; $r->send_http_header unless MP2; - + # send the file if (MP2) { $r->sendfile($source); } else { $r->send_fd($fh); } - + if (!MP2) { # close the file and go home close $fh; } - + if ($delete_after) { unlink $source or warn "failed to unlink $source after sending: $!"; } - + return; # (see comment on return statement in do_reply_with_redirect, below.) } @@ -306,17 +306,17 @@ Handler for reply_with_redirect(), used by go(). DO NOT CALL THIS METHOD DIRECTL sub do_reply_with_redirect { my ($self, $url) = @_; my $r = $self->r; - + $r->status(MP2 ? Apache2::Const::REDIRECT : Apache::Constants::REDIRECT); $r->headers_out->{"Location"} = $url; $r->send_http_header unless MP2; - + return; # we need to explicitly return noting here, otherwise we return $url under Apache2. # the return value from the mod_perl handler is used to set the HTTP status code, # but we're setting it explicitly above. i think we should dispense with setting it # with the return value altogether, and always do it with $r->status. the other way # is too oblique and error-prone. this is probably a FIXME. - # + # # Apache::WeBWorK::handler always returns the value it got from WeBWorK::dispatch # WeBWorK::dispatch always returns the value it got from WW::ContentGenerator::go # WW::ContentGenerator::go works like this: @@ -358,7 +358,7 @@ pre_header_initialize(). sub reply_with_file { my ($self, $type, $source, $name, $delete_after) = @_; $delete_after ||= ""; - + $self->{reply_with_file} = { type => $type, source => $source, @@ -379,7 +379,7 @@ pre_header_initialize(). sub reply_with_redirect { my ($self, $url) = @_; - + $self->{reply_with_redirect} = $url; } @@ -395,9 +395,9 @@ Must be called before the message() template escape is invoked. sub addmessage { - #addmessages takes html so we use htmlscrubber to get rid of + #addmessages takes html so we use htmlscrubber to get rid of # any scripts or html comments. However, we leave everything else - # by default. + # by default. my ($self, $message) = @_; return unless defined($message); @@ -445,11 +445,11 @@ sub addbadmessage { } =item prepare_activity_entry() - + Prepare a string to be sent to the activity log, if it is turned on. This can be overriden by different modules. - -=cut + +=cut sub prepare_activity_entry { @@ -498,7 +498,7 @@ type. sub header { my $self = shift; my $r = $self->r; - + $r->content_type("text/html; charset=utf-8"); $r->send_http_header unless MP2; return MP2 ? Apache2::Const::OK : Apache::Constants::OK; @@ -581,7 +581,7 @@ sub content { my ($self) = @_; my $r = $self->r; my $ce = $r->ce; - + my $themesDir = $ce->{webworkDirs}{themes}; my $theme = $r->param("theme") || $ce->{defaultTheme}; $theme = $ce->{defaultTheme} if $theme =~ m!(?:^|/)\.\.(?:/|$)!; @@ -601,10 +601,10 @@ sub content { "simple.conf and on the course configuration page.\n" } else { $theme = HTML::Entities::encode_entities($theme); - die "Neither the theme $theme nor the defaultTheme math4 are available. ". + die "Neither the theme $theme nor the defaultTheme math4 are available. ". "Please notify your site administrator that the structure of the ". "themes directory needs attention."; - + } } template($templateFile, $self); @@ -666,10 +666,10 @@ sub links { my $authen = $r->authen; my $authz = $r->authz; my $urlpath = $r->urlpath; - + # we don't currently have any links to display if the user's not logged in. this may change, though. #return "" unless $authen->was_verified; - + # grab some interesting data from the request my $courseID = $urlpath->arg("courseID"); my $userID = $r->param('user'); @@ -682,7 +682,7 @@ sub links { my $prettyAchievementID = $achievementID; $prettySetID =~ s/_/ /g if defined $prettySetID; $prettyAchievementID =~ s/_/ /g if defined $prettyAchievementID; - + my $prettyProblemID = $problemID; # it's possible that the setID and the problemID are invalid, since they're just taken from the URL path info @@ -700,25 +700,25 @@ sub links { $problemID = undef; } } - + # experimental subroutine for generating links, to clean up the rest of the # code. ignore for now. (this is a closure over $self.) my $makelink = sub { my ($module, %options) = @_; - + my $urlpath_args = $options{urlpath_args} || {}; my $systemlink_args = $options{systemlink_args} || {}; my $text = HTML::Entities::encode_entities($options{text}); my $active = $options{active}; my %target = ($options{target} ? (target => $options{target}) : ()); - + my $new_urlpath = $self->r->urlpath->newFromModule($module, $r, %$urlpath_args); my $new_systemlink = $self->systemLink($new_urlpath, %$systemlink_args); defined $text or $text = $new_urlpath->name; #too clever - - - # try to set $active automatically by comparing + + + # try to set $active automatically by comparing if (not defined $active) { if ($urlpath->module eq $new_urlpath->module) { my @args = sort keys %{{$urlpath->args}}; @@ -736,7 +736,7 @@ sub links { $active = 0; } } - + my $new_anchor; if ($active) { # add active class for current location @@ -744,14 +744,14 @@ sub links { } else { $new_anchor = CGI::a({href=>$new_systemlink, %target}, "$text"); } - + return $new_anchor; }; - + # to make things more concise my $pfx = "WeBWorK::ContentGenerator::"; my %args = ( courseID => $courseID ); - + # we'd like to preserve displayMode and showOldAnswers between pages, and we # don't have a general way of preserving non-authen params between requests, # so here is the hack: @@ -769,7 +769,7 @@ sub links { # always this value before using it. my %systemlink_args; $systemlink_args{params} = \%params if %params; - + print CGI::start_ul(); print CGI::start_li({class => "nav-header"}); print CGI::h2($r->maketext("Main Menu")); @@ -777,7 +777,7 @@ sub links { print CGI::start_li(); # Courses print &$makelink("${pfx}Home", text=>$r->maketext("Courses"), systemlink_args=>{authen=>0}); print CGI::end_li(); # end Courses - + if (defined $courseID) { if ($authen->was_verified) { print CGI::start_li(); # Homework Sets @@ -808,7 +808,7 @@ sub links { print CGI::start_li(); print CGI::start_ul(); print CGI::start_li(); # $problemID - print &$makelink("${pfx}Problem", text=>$r->maketext("Problem [_1]", $prettyProblemID), urlpath_args=>{%args,setID=>$setID,problemID=>$problemID}, systemlink_args=>\%systemlink_args); + print &$makelink("${pfx}Problem", text=>$r->maketext("Problem [_1]", $prettyProblemID), urlpath_args=>{%args,setID=>$setID,problemID=>$problemID}, systemlink_args=>\%systemlink_args); print CGI::end_li(); # end $problemID print CGI::end_ul(); print CGI::end_li(); @@ -817,31 +817,31 @@ sub links { print CGI::end_li(); # end Homework Sets } - + print CGI::li(&$makelink("${pfx}Options", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); - + print CGI::li(&$makelink("${pfx}Grades", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); - + if ($ce->{achievementsEnabled}) { - print CGI::li(&$makelink("${pfx}Achievements", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); + print CGI::li(&$makelink("${pfx}Achievements", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); } if ($authz->hasPermissions($userID, "access_instructor_tools")) { $pfx .= "Instructor::"; - + print CGI::start_li(); # Instructor Tools print &$makelink("${pfx}Index", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args); print CGI::end_li(); print CGI::start_li(); print CGI::start_ul(); - + #class list editor print CGI::li(&$makelink("${pfx}UserList", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)) if $ce->{showeditors}->{classlisteditor1}; print CGI::li(&$makelink("${pfx}UserList2", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)) if $ce->{showeditors}->{classlisteditor2}; - + # Homework Set Editor print CGI::li(&$makelink("${pfx}ProblemSetList", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)) if $ce->{showeditors}->{homeworkseteditor1}; @@ -872,10 +872,10 @@ sub links { if $ce->{showeditors}->{pgproblemeditor1}; print CGI::li(&$makelink("${pfx}PGProblemEditor2", text=>"$prettyProblemID", urlpath_args=>{%args,setID=>$setID,problemID=>$problemID}, systemlink_args=>\%systemlink_args, target=>"WW_Editor2")) if $ce->{showeditors}->{pgproblemeditor2};; - + print CGI::li(&$makelink("${pfx}PGProblemEditor3", text=>"--$prettyProblemID", urlpath_args=>{%args,setID=>$setID,problemID=>$problemID}, systemlink_args=>\%systemlink_args, target=>"WW_Editor3")) if $ce->{showeditors}->{pgproblemeditor3};; - + print CGI::li(&$makelink("${pfx}SimplePGEditor", text=>"$prettyProblemID", urlpath_args=>{%args,setID=>$setID,problemID=>$problemID}, systemlink_args=>\%systemlink_args, target=>"Simple_Editor")) if $ce->{showeditors}->{simplepgeditor};; print CGI::end_ul(); @@ -889,11 +889,11 @@ sub links { print CGI::end_ul(); print CGI::end_li(); } - + print CGI::end_ul(); print CGI::end_li(); } - + print CGI::li(&$makelink("${pfx}SetMaker", text=>$r->maketext("Library Browser"), urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)) if $ce->{showeditors}->{librarybrowser1}; print CGI::li(&$makelink("${pfx}SetMaker2", text=>$r->maketext("Library Browser 2"), urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)) @@ -920,7 +920,7 @@ sub links { print CGI::end_ul(); } print CGI::end_li(); # end Stats - + print CGI::start_li(); # Student Progress print &$makelink("${pfx}StudentProgress", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args); if ($userID ne $eUserID or defined $setID) { @@ -938,11 +938,11 @@ sub links { print CGI::end_ul(); } print CGI::end_li(); # end Student Progress - + if ($authz->hasPermissions($userID, "score_sets")) { print CGI::li(&$makelink("${pfx}Scoring", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); } - + #Show achievement editor for instructors if ($ce->{achievementsEnabled} && $authz->hasPermissions($userID, "edit_achievements")) { print CGI::li(&$makelink("${pfx}AchievementList", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); @@ -954,17 +954,17 @@ sub links { print CGI::end_ul(); print CGI::end_li(); } - + } if ($authz->hasPermissions($userID, "send_mail")) { print CGI::li(&$makelink("${pfx}SendMail", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); } - + if ($authz->hasPermissions($userID, "manage_course_files")) { print CGI::li(&$makelink("${pfx}FileManager", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); } - + if ($authz->hasPermissions($userID, "manage_course_files")) { print CGI::li(&$makelink("${pfx}Config", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args)); } @@ -978,19 +978,19 @@ sub links { print CGI::end_ul(); print CGI::end_li(); # end Instructor Tools } # /* access_instructor_tools */ - + if (exists $ce->{webworkURLs}{bugReporter} and $ce->{webworkURLs}{bugReporter} ne "" and $authz->hasPermissions($userID, "report_bugs")) { print CGI::li({class=>'divider', 'aria-hidden'=>'true'},""); print CGI::li(CGI::a({href=>$ce->{webworkURLs}{bugReporter}}, $r->maketext("Report bugs"))); } - + } # /* authentication was_verified */ - + } # /* defined $courseID */ print CGI::end_ul(); - + return ""; } @@ -1010,19 +1010,19 @@ sub loginstatus { my $authen = $r->authen; my $urlpath = $r->urlpath; #This will contain any extra parameters which are needed to make - # the page function properly. This will normally be empty. + # the page function properly. This will normally be empty. my $extraStopActingParams = $r->{extraStopActingParams}; if ($authen and $authen->was_verified) { my $courseID = $urlpath->arg("courseID"); my $userID = $r->param("user"); my $eUserID = $r->param("effectiveUser"); - + $extraStopActingParams->{effectiveUser} = $userID; my $stopActingURL = $self->systemLink($urlpath, # current path params=>$extraStopActingParams); my $logoutURL = $self->systemLink($urlpath->newFromModule(__PACKAGE__ . "::Logout", $r, courseID => $courseID)); - + if ($eUserID eq $userID) { print $r->maketext("Logged in as [_1].", HTML::Entities::encode_entities($userID)) . CGI::a({href=>$logoutURL}, $r->maketext("Log Out")); } else { @@ -1033,7 +1033,7 @@ sub loginstatus { } else { print $r->maketext("Not logged in."); } - + return ""; } @@ -1095,9 +1095,9 @@ associated with the current request. sub path { my ($self, $args) = @_; my $r = $self->r; - + my @path; - + my $urlpath = $r->urlpath; do { my $name = $urlpath->name; @@ -1113,13 +1113,13 @@ sub path { } unshift @path, $name, $r->location . $urlpath->path; } while ($urlpath = $urlpath->parent); - + $path[$#path] = ""; # we don't want the last path element to be a link - + #print "\n\n"; print $self->pathMacro($args, @path); #print "\n"; - + return ""; } @@ -1136,7 +1136,7 @@ Print links to siblings of the current object. =item footer() -by ghe3 - + combines timestamp() and other elements of the footer, including the copyright, into one output subroutine, =cut @@ -1149,15 +1149,15 @@ sub footer(){ my $theme = $ce->{defaultTheme}||"unknown -- set defaultTheme in localOverides.conf"; my $copyright_years = $ce->{WW_COPYRIGHT_YEARS}||"1996-2019"; print CGI::div({-id=>"last-modified"}, $r->maketext("Page generated at [_1]", timestamp($self))); - print CGI::div({-id=>"copyright"}, $r->maketext("WeBWorK © [_1]| theme: [_2] | ww_version: [_3] | pg_version [_4]|", - $copyright_years,$theme, $ww_version, $pg_version), - CGI::a({-href=>"http://webwork.maa.org/"}, + print CGI::div({-id=>"copyright"}, $r->maketext("WeBWorK © [_1]| theme: [_2] | ww_version: [_3] | pg_version [_4]|", + $copyright_years,$theme, $ww_version, $pg_version), + CGI::a({-href=>"http://webwork.maa.org/"}, $r->maketext("The WeBWorK Project"), )); return "" } - + =item timestamp() Defined in this package. @@ -1197,13 +1197,13 @@ $self->{status_message}, if it is present. sub message { my ($self) = @_; - + print "\n\n"; print $self->{status_message} if exists $self->{status_message}; - + print "\n"; - + return ""; } @@ -1226,7 +1226,7 @@ sub title { my $urlpath = $r->urlpath; # If the urlpath name is the courseID, and if the course has - # a course title then display that instead. + # a course title then display that instead. if (defined($urlpath->arg("courseID")) && $urlpath->name eq $urlpath->arg("courseID") && $db->settingExists('courseTitle')) { @@ -1240,7 +1240,7 @@ sub title { print $name; #print "\n"; } - + return ""; } @@ -1264,7 +1264,7 @@ sub warnings { $warnings = Encode::decode_utf8($warnings); print $self->warningOutput($warnings) if $warnings; print "\n"; - + return ""; } @@ -1309,7 +1309,7 @@ sub url { my $ce = $self->r->ce; my $type = $args->{type}; my $name = $args->{name}; - + if ($type eq "webwork") { # we have to build this here (and not in say defaults.conf) because # defaultTheme will chage as late as simple.conf @@ -1355,7 +1355,7 @@ template: sub if_can { my ($self, $arg) = @_; - + if ($arg eq "floobar") { return 0; } else { @@ -1367,7 +1367,7 @@ template: sub if_can { my ($self, $arg) = @_; - + return $self->can($arg) ? 1 : 0; } @@ -1390,7 +1390,7 @@ retrieve the result of the last call to WeBWorK::Authen::verify(). sub if_loggedin { my ($self, $arg) = @_; - + #return $arg; return 0 unless $self->r->authen; return $self->r->authen->was_verified() ? $arg : !$arg; @@ -1409,7 +1409,7 @@ redefined to handle that variance: sub if_message { my ($self, $arg) = @_; - + my $status = $self->{processReturnValue}; if ($status != 0) { return $arg; @@ -1422,7 +1422,7 @@ redefined to handle that variance: sub if_message { my ($self, $arg) = @_; - + if (exists $self->{status_message}) { return $arg; } else { @@ -1445,8 +1445,8 @@ sub if_warnings { my ($self, $arg) = @_; my $r = $self->r; - if ( (MP2 ? $r->notes->get("warnings") : $r->notes("warnings")) - or ($self->{pgerrors}) ) + if ( (MP2 ? $r->notes->get("warnings") : $r->notes("warnings")) + or ($self->{pgerrors}) ) { return $arg; } else { @@ -1513,7 +1513,7 @@ sub pathMacro { my $r = $self->r; my %args = %$args; $args{style} = "text" if $args{textonly}; - + my $auth = $self->url_authen_args; my $sep; if ($args{style} eq "image") { @@ -1521,7 +1521,7 @@ sub pathMacro { } else { $sep = $args{text}; } - + my @result; while (@path) { my $name = shift @path; @@ -1541,7 +1541,7 @@ sub pathMacro { } } } - + return join($sep, @result), "\n"; } @@ -1564,10 +1564,10 @@ we have systemLink(). sub siblingsMacro { my ($self, @siblings) = @_; - + my $auth = $self->url_authen_args; my $sep = CGI::br(); - + my @result; while (@siblings) { my $name = shift @siblings; @@ -1578,7 +1578,7 @@ sub siblingsMacro { ? CGI::span( {id=>$id}, CGI::a({-href=>"$url?$auth"}, $name) ) : CGI::span( {id=>$id},$name ); } - + return join($sep, @result) . "\n"; } @@ -1619,7 +1619,7 @@ sub navMacro { ? CGI::a({-href=>"$url?$auth$tail", -class=>"nav_button"}, $html) : CGI::span({-class=>"gray_button"}, $html); } - + return join($args{separator}, @result) . "\n"; } @@ -1670,10 +1670,10 @@ sub feedbackMacro { my $r = $self->r; my $authz = $r->authz; my $userID = $r->param("user"); - + # don't do anything unless the user has permission to return "" unless $authz->hasPermissions($userID, "submit_feedback"); - + my $feedbackURL = $r->ce->{courseURLs}{feedbackURL}; my $feedbackFormURL = $r->ce->{courseURLs}{feedbackFormURL}; if (defined $feedbackURL and $feedbackURL ne "") { @@ -1691,26 +1691,26 @@ sub feedbackMacro_email { my $ce = $r->ce; my $urlpath = $r->urlpath; my $courseID = $urlpath->arg("courseID"); - + # feedback form url my $feedbackPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Feedback", $r, courseID => $courseID); my $feedbackURL = $self->systemLink($feedbackPage, authen => 0); # no authen info for form action my $feedbackName = $r->maketext($ce->{feedback_button_name}) || $r->maketext("Email instructor"); - + my $result = CGI::start_form(-method=>"POST", -action=>$feedbackURL) . "\n"; #This is being used on forms with hidden_authen_fields already included # in many pages so we need to change the fields to be hidden my $hiddenFields = $self->hidden_authen_fields; $hiddenFields =~ s/\"hidden_/\"email-hidden_/g; $result .= $hiddenFields."\n"; - + while (my ($key, $value) = each %params) { next if $key eq 'pg_object'; # not used in internal feedback mechanism $result .= CGI::hidden($key, $value) . "\n"; } $result .= CGI::p(CGI::submit(-name=>"feedbackForm", -value=>$feedbackName)); $result .= CGI::end_form() . "\n"; - + return $result; } @@ -1718,19 +1718,17 @@ sub feedbackMacro_form { my ($self, $feedbackFormURL, %params) = @_; my $r = $self->r; my $ce = $r->ce; - my $urlpath = $r->urlpath; - my $courseID = $urlpath->arg("courseID"); - + # feedback form url my $feedbackName = $r->maketext($ce->{feedback_button_name}) || $r->maketext("Email instructor"); - + my $result = CGI::start_form(-method=>"POST", -action=>$feedbackFormURL,-target=>"WW_info") . "\n"; - $result .= $self->hidden_authen_fields . "\n"; + $result .= $self->hidden_authen_fields . "\n"; while (my ($key, $value) = each %params) { if ($key eq 'pg_object') { - my $tmp = $value->{body_text}; + my $tmp = $value->{body_text}; $tmp .= CGI::p(CGI::b("Note: "). CGI::i($value->{result}->{msg})) if $value->{result}->{msg} ; $result .= CGI::hidden($key, encode_base64($tmp, "") ); } else { @@ -1739,7 +1737,7 @@ sub feedbackMacro_form { } $result .= CGI::p({-align=>"left"}, CGI::submit(-name=>"feedbackForm", -value=>$feedbackName)); $result .= CGI::end_form() . "\n"; - + return $result; } @@ -1773,16 +1771,16 @@ list is empty), taking data from the current request. sub hidden_fields { my ($self, @fields) = @_; my $r = $self->r; - + @fields = $r->param unless @fields; - + my $html = ""; foreach my $param (@fields) { my @values = $r->param($param); foreach my $value (@values) { next unless defined($value); -# $html .= CGI::hidden($param, $value); # (can't name these items when using real CGI) - $html .= CGI::hidden(-name=>$param, -default=>$value, -id=>"hidden_".$param); # (can't name these items when using real CGI) +# $html .= CGI::hidden($param, $value); # (can't name these items when using real CGI) + $html .= CGI::hidden(-name=>$param, -default=>$value, -id=>"hidden_".$param); # (can't name these items when using real CGI) } } @@ -1798,7 +1796,7 @@ authentication. sub hidden_authen_fields { my ($self) = @_; - + return $self->hidden_fields("user", "effectiveUser", "key", "theme"); } @@ -1828,9 +1826,9 @@ fields. sub hidden_state_fields { my ($self) = @_; - + return $self->hidden_authen_fields(); - + # other things that may be state data: #$self->hidden_fields("displayMode", "showOldAnswers", "showCorrectAnswers", "showHints", "showSolutions"); } @@ -1846,9 +1844,9 @@ the current request. sub url_args { my ($self, @fields) = @_; my $r = $self->r; - + @fields = $r->param unless @fields; - + my @pairs; foreach my $param (@fields) { my @values = $r->param($param); @@ -1856,7 +1854,7 @@ sub url_args { push @pairs, uri_escape_utf8($param) . "=" . uri_escape($value); } } - + return join("&", @pairs); } @@ -1869,7 +1867,7 @@ authentication. sub url_authen_args { my ($self) = @_; - + return $self->url_args("user", "effectiveUser", "key", "theme"); } @@ -1882,9 +1880,9 @@ state. Currently includes authentication fields and display option fields. sub url_state_args { my ($self) = @_; - + return $self->url_authen_args; - + # other things that may be state data: #$self->url_args("displayMode", "showOldAnswers", "showCorrectAnswers", "showHints", "showSolutions"); } @@ -1900,7 +1898,7 @@ sub url_state_args { # #sub url_display_args { # my ($self) = @_; -# +# # return $self->url_args("displayMode", "showOldAnswer"); #} @@ -1919,7 +1917,7 @@ sub url_state_args { # my ($self, $begin, $middle, $end, $qr_omit) = @_; # my $r=$self->r; # my @form_data = $r->param; -# +# # my $return_string = ""; # foreach my $name (@form_data) { # next if ($qr_omit and $name =~ /$qr_omit/); @@ -1933,7 +1931,7 @@ sub url_state_args { # $return_string .= "$begin$name$middle$value$end"; # } # } -# +# # return $return_string; #} @@ -1990,7 +1988,7 @@ via email. sub systemLink { my ($self, $urlpath, %options) = @_; my $r = $self->r; - + my %params = (); if (exists $options{params}) { if (ref $options{params} eq "HASH") { @@ -2002,7 +2000,7 @@ sub systemLink { croak "option 'params' is not a hashref or an arrayref"; } } - + my $authen = exists $options{authen} ? $options{authen} : 1; if ($authen) { $params{user} = undef unless exists $params{user}; @@ -2010,16 +2008,16 @@ sub systemLink { $params{key} = undef unless exists $params{key}; $params{theme} = undef unless exists $params{theme}; } - + my $url; - + $url = $r->ce->{apache_root_url} if $options{use_abs_url}; $url .= $r->location . $urlpath->path; my $first = 1; - + foreach my $name (keys %params) { my $value = $params{$name}; - + my @values; if (defined $value) { if (ref $value eq "ARRAY") { @@ -2040,7 +2038,7 @@ sub systemLink { warn "Parameters are ", join("|",$r->param()); } - + if (@values) { if ($first) { $url .= "?"; @@ -2051,7 +2049,7 @@ sub systemLink { $url .= join "&", map { "$name=".HTML::Entities::encode_entities($_) } @values; } } - + return $url; } @@ -2134,7 +2132,7 @@ sub errorOutput($$$) { if (ref($details) =~ /SCALAR/i) { $details = [$$details]; } elsif (ref($details) =~/ARRAY/i) { - # no change needed + # no change needed } else { $details = [$details]; } @@ -2146,13 +2144,13 @@ sub errorOutput($$$) { CGI::p(CGI::code($error)), CGI::h3("Error details"), - + CGI::start_code(), CGI::start_p(), @{ $details }, - #CGI::code(CGI::p(@expandedDetails)), + #CGI::code(CGI::p(@expandedDetails)), # not using inclusive CGI calls here saves about 30Meg of memory! CGI::end_p(),CGI::end_code(), - + CGI::h3($r->maketext("Request information")), CGI::table({border=>"1"}, CGI::Tr({},CGI::td($r->maketext("Time")), CGI::td($time)), @@ -2162,8 +2160,8 @@ sub errorOutput($$$) { CGI::table($headers), )), ), - ; - + ; + } =item warningMessage @@ -2175,10 +2173,10 @@ Used to print out a generic warning message at the top of the page sub warningMessage { my $self = shift; my $r = $self->r; - + return CGI::b($r->maketext("Warning")), ' -- ', $r->maketext("There may be something wrong with this question. Please inform your instructor including the warning messages below."); - + } @@ -2206,15 +2204,15 @@ sub warningOutput($$) { '*' => 1, } ); - + foreach my $warning (@warnings) { # Since these warnings have html they look better scrubbed - #$warning = HTML::Entities::encode_entities($warning); + #$warning = HTML::Entities::encode_entities($warning); $warning = $scrubber->scrub($warning); $warning = CGI::li(CGI::code($warning)); } $warnings = join("", @warnings); - + my $time = time2str("%a %b %d %H:%M:%S %Y", time); my $method = $r->method; my $uri = $r->uri; @@ -2222,7 +2220,7 @@ sub warningOutput($$) { # my %headers = $r->headers_in; # join("", map { CGI::Tr(CGI::td(CGI::small($_)), CGI::td(CGI::small($headers{$_}))) } keys %headers); #}; - + return CGI::h2($r->maketext("WeBWorK Warnings")), CGI::p($r->maketext('WeBWorK has encountered warnings while processing your request. If this occured when viewing a problem, it was likely caused by an error or ambiguity in that problem. Otherwise, it may indicate a problem with the WeBWorK system itself. If you are a student, report these warnings to your professor to have them corrected. If you are a professor, please consult the warning output below for more information.')), @@ -2317,11 +2315,11 @@ sub createEmailSenderTransportSMTP { # debug => 1, }); } -# warn "port is ", $transport->port(); -# warn "ssl is ", $transport->ssl(); +# warn "port is ", $transport->port(); +# warn "ssl is ", $transport->ssl(); # warn "tls_allowed is ", $ce->{mail}->{tls_allowed}//''; # warn " smtpPort is set to ", $ce->{mail}->{smtpPort}//''; - + return $transport; } =head1 AUTHOR diff --git a/lib/WeBWorK/ContentGenerator/Feedback.pm b/lib/WeBWorK/ContentGenerator/Feedback.pm index dd5708afc7..8037180ada 100644 --- a/lib/WeBWorK/ContentGenerator/Feedback.pm +++ b/lib/WeBWorK/ContentGenerator/Feedback.pm @@ -109,45 +109,8 @@ sub body { } # generate context URLs - my $emailableURL; - my $returnURL; - if ($user) { - my $modulePath; - my @args; - if ($set) { - if ($problem) { - $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", $r, - courseID => $r->urlpath->arg("courseID"), - setID => $set->set_id, - problemID => $problem->problem_id, - ); - @args = qw/displayMode showOldAnswers showCorrectAnswers showHints showSolutions/; - } else { - $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", $r, - courseID => $r->urlpath->arg("courseID"), - setID => $set->set_id, - ); - @args = (); - } - } else { - $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", $r, - courseID => $r->urlpath->arg("courseID"), - ); - @args = (); - } - $emailableURL = $self->systemLink($modulePath, - authen => 0, - params => [ "effectiveUser", @args ], - use_abs_url => 1, - ); - $returnURL = $self->systemLink($modulePath, - authen => 1, - params => [ @args ], - ); - } else { - $emailableURL = "(not available)"; - $returnURL = ""; - } + my ($emailableURL, $returnURL) = $self->generateURLs(); + my $homeModulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::Home", $r); my $systemURL = $self->systemLink($homeModulePath, authen=>0, use_abs_url=>1); diff --git a/lib/WeBWorK/ContentGenerator/Problem.pm b/lib/WeBWorK/ContentGenerator/Problem.pm index 0131d9c882..a990bcad2d 100644 --- a/lib/WeBWorK/ContentGenerator/Problem.pm +++ b/lib/WeBWorK/ContentGenerator/Problem.pm @@ -2,12 +2,12 @@ # WeBWorK Online Homework Delivery System # Copyright © 2000-2018 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Problem.pm,v 1.225 2010/05/28 21:29:48 gage Exp $ -# +# # 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 @@ -20,7 +20,7 @@ use base qw(WeBWorK::ContentGenerator); use WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil; # not needed? =head1 NAME - + WeBWorK::ContentGenerator::Problem - Allow a student to interact with a problem. =cut @@ -54,34 +54,34 @@ binmode(STDOUT, ":utf8"); ################################################################################ # Standard params: -# +# # user - user ID of real user # key - session key # effectiveUser - user ID of effective user -# +# # Integration with PGProblemEditor: -# +# # editMode - if set, indicates alternate problem source location. # can be "temporaryFile" or "savedFile". -# +# # sourceFilePath - path to file to be edited # problemSeed - force problem seed to value # success - success message to display # failure - failure message to display -# +# # Rendering options: -# +# # displayMode - name of display mode to use -# +# # showOldAnswers - request that last entered answer be shown (if allowed) # showCorrectAnswers - request that correct answers be shown (if allowed) # showHints - request that hints be shown (if allowed) # showSolutions - request that solutions be shown (if allowed) -# +# # Problem interaction: -# +# # AnSwEr# - answer blanks in problem -# +# # redisplay - name of the "Redisplay Problem" button # submitAnswers - name of "Submit Answers" button # checkAnswers - name of the "Check Answers" button @@ -94,10 +94,10 @@ binmode(STDOUT, ":utf8"); # Subroutines to determine if a user "can" perform an action. Each subroutine is # called with the following arguments: -# +# # ($self, $User, $EffectiveUser, $Set, $Problem) -# Note that significant parts of the "can" methods are lifted into the +# Note that significant parts of the "can" methods are lifted into the # GatewayQuiz module. It isn't direct, however, because of the necessity # of dealing with versioning there. @@ -111,7 +111,7 @@ sub can_showOldAnswers { sub can_showCorrectAnswers { my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; my $authz = $self->r->authz; - + return after($Set->answer_date) || @@ -148,7 +148,7 @@ sub can_showPGInfo { sub can_showResourceInfo { my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; my $authz = $self->r->authz; - + return $authz->hasPermissions($User->user_id, "show_resource_info") ; @@ -157,14 +157,14 @@ sub can_showResourceInfo { sub can_showHints { my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; my $authz = $self->r->authz; - + return !$Set->hide_hint; } sub can_showSolutions { my ($self, $User, $EffectiveUser, $Set, $Problem) = @_; my $authz = $self->r->authz; - + return after($Set->answer_date) || @@ -201,14 +201,14 @@ sub can_checkAnswers { my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; my $authz = $self->r->authz; my $thisAttempt = $submitAnswers ? 1 : 0; - + # if we can record answers then we dont need to be able to check them - # unless we have that specific permission. - if ($self->can_recordAnswers($User,$EffectiveUser,$Set,$Problem,$submitAnswers) + # unless we have that specific permission. + if ($self->can_recordAnswers($User,$EffectiveUser,$Set,$Problem,$submitAnswers) && !$authz->hasPermissions($User->user_id, "can_check_and_submit_answers")) { return 0; } - + if (before($Set->open_date)) { return $authz->hasPermissions($User->user_id, "check_answers_before_open_date"); } elsif (between($Set->open_date, $Set->due_date)) { @@ -246,17 +246,17 @@ sub can_useMathQuill { return $ce->{pg}->{specialPGEnvironmentVars}->{entryAssist} eq 'MathQuill'; } - + sub can_showMeAnother { - # PURPOSE: subroutine to check if showMeAnother + # PURPOSE: subroutine to check if showMeAnother # button should be allowed; note that this is done - # *before* the check to see if showMeAnother is + # *before* the check to see if showMeAnother is # possible. my ($self, $User, $EffectiveUser, $Set, $Problem, $submitAnswers) = @_; my $ce = $self->r->ce; - # if the showMeAnother button isn't enabled in the course configuration, + # if the showMeAnother button isn't enabled in the course configuration, # don't show it under any circumstances (not even for the instructor) return 0 unless($ce->{pg}->{options}->{enableShowMeAnother}); @@ -264,7 +264,7 @@ sub can_showMeAnother { my %showMeAnother = %{ $self->{showMeAnother} }; if (after($Set->open_date)) { - # if $showMeAnother{TriesNeeded} is somehow not an integer or if its -2, use the default value + # if $showMeAnother{TriesNeeded} is somehow not an integer or if its -2, use the default value $showMeAnother{TriesNeeded} = $ce->{pg}->{options}->{showMeAnotherDefault} if ($showMeAnother{TriesNeeded} !~ /^[+-]?\d+$/ || $showMeAnother{TriesNeeded} == -2); # if SMA is just not permitted for the problem, don't show it @@ -283,14 +283,14 @@ sub can_showMeAnother { # if we've gotten this far, the button is enabled globally and for the problem; check if the student has either # not submitted enough answers yet or has used the SMA button too many times - if ($attempts_used < $showMeAnother{TriesNeeded} + if ($attempts_used < $showMeAnother{TriesNeeded} or ($showMeAnother{Count}>=$showMeAnother{MaxReps} and $showMeAnother{MaxReps}>-1)) { return 0; } else { return 1; } } else { - # otherwise the set hasn't been opened yet, so we can't use showMeAnother + # otherwise the set hasn't been opened yet, so we can't use showMeAnother return 0;} } @@ -310,7 +310,7 @@ sub attemptResults { my $showSummary = shift; my $showAttemptPreview = shift // 1; my $ce = $self->r->ce; - + # to make grabbing these options easier, we'll pull them out now... my %imagesModeOptions = %{$ce->{pg}->{displayModeOptions}->{images}}; @@ -329,7 +329,7 @@ sub attemptResults { my $answers = $pg->{answers}; my $showEvaluatedAnswers = $ce->{pg}->{options}->{showEvaluatedAnswers}//''; -# Create AttemptsTable object +# Create AttemptsTable object my $tbl = WeBWorK::Utils::AttemptsTable->new( $answers, answersSubmitted => 1, @@ -347,8 +347,8 @@ sub attemptResults { maketext => WeBWorK::Localize::getLoc($ce->{language}), ); # render equation images - my $answerTemplate = $tbl->answerTemplate; - # answerTemplate collects all the formulas to be displayed in the attempts table + my $answerTemplate = $tbl->answerTemplate; + # answerTemplate collects all the formulas to be displayed in the attempts table # answerTemplate also collects the correct_ids and incorrect_ids $tbl->imgGen->render(refresh => 1) if $tbl->displayMode eq 'images'; # after all of the formulas have been collected the render command creates png's for them @@ -390,18 +390,18 @@ sub pre_header_initialize { my $effectiveUserName = $r->param('effectiveUser'); my $key = $r->param('key'); my $editMode = $r->param("editMode"); - + my $user = $db->getUser($userName); # checked die "record for user $userName (real user) does not exist." unless defined $user; - + my $effectiveUser = $db->getUser($effectiveUserName); # checked die "record for user $effectiveUserName (effective user) does not exist." unless defined $effectiveUser; - + # obtain the merged set for $effectiveUser - my $set = $db->getMergedSet($effectiveUserName, $setName); - + my $set = $db->getMergedSet($effectiveUserName, $setName); + # check that the set is valid; # $self->{invalidSet} is set by ContentGenerator.pm die($self->{invalidSet}) if $self->{invalidSet}; @@ -411,10 +411,10 @@ sub pre_header_initialize { # or if the set is the "Undefined_set" $self->{isOpen} = $self->{isOpen} || $setName eq "Undefined_Set"; - + # or if the set is past the answer date $self->{isOpen} = $self->{isOpen} || time >= $set->answer_date; - + my $isClosed = 0; # now we check the reasons why it might be closed unless ($self->{isOpen}) { @@ -427,10 +427,10 @@ sub pre_header_initialize { is_jitar_problem_closed($db,$ce,$effectiveUserName,$set->set_id,$problemNumber)); } - # isOpen overrides $isClosed. + # isOpen overrides $isClosed. $self->{isOpen} = $self->{isOpen} || !$isClosed; - - die("You do not have permission to view unopened sets") unless $self->{isOpen}; + + die("You do not have permission to view unopened sets") unless $self->{isOpen}; # Database fix (in case of undefined visiblity state values) # this is only necessary because some people keep holding to ww1.9 which did not have a visible field @@ -444,27 +444,27 @@ sub pre_header_initialize { # don't do anything just yet, maybe we're a professor and we're # fabricating a set or haven't assigned it to ourselves just yet } - # When a set is created enable_reduced_scoring is null, so we have to set it + # When a set is created enable_reduced_scoring is null, so we have to set it if ( $set and $set->enable_reduced_scoring ne "0" and $set->enable_reduced_scoring ne "1") { my $globalSet = $db->getGlobalSet($set->set_id); $globalSet->enable_reduced_scoring("0"); # defaults to disabled $db->putGlobalSet($globalSet); $set = $db->getMergedSet($effectiveUserName, $setName); } - - + + # obtain the merged problem for $effectiveUser my $problem = $db->getMergedProblem($effectiveUserName, $setName, $problemNumber); # checked - + # A very hacky and temporary solution to the max_attempts problem # if($problem->max_attempts == ""){ # $problem->max_attempts = -1; # } - + if ($authz->hasPermissions($userName, "modify_problem_sets")) { # professors are allowed to fabricate sets and problems not # assigned to them (or anyone). this allows them to use the - # editor to + # editor to # if a User Set does not exist for this user and this set # then we check the Global Set @@ -481,7 +481,7 @@ sub pre_header_initialize { $set->psvn(0); } } - + # if that is not yet defined obtain the global problem, # convert it to a user problem, and add fake user data unless (defined $problem) { @@ -509,12 +509,12 @@ sub pre_header_initialize { $problem->num_incorrect(0); } } - + # now we're sure we have valid UserSet and UserProblem objects # yay! - + # now deal with possible editor overrides: - + # if the caller is asking to override the source file, and # editMode calls for a temporary file, do so my $sourceFilePath = $r->param("sourceFilePath"); @@ -522,8 +522,8 @@ sub pre_header_initialize { die "sourceFilePath is unsafe!" unless path_is_subdir($sourceFilePath, $ce->{courseDirs}->{templates}, 1); # 1==path can be relative to dir $problem->source_file($sourceFilePath); } - - # if the problem does not have a source file or no source file has been passed in + + # if the problem does not have a source file or no source file has been passed in # then this is really an invalid problem (probably from a bad URL) $self->{invalidProblem} = not (defined $sourceFilePath or $problem->source_file); @@ -531,7 +531,7 @@ sub pre_header_initialize { my $problemSeed = $r->param("problemSeed"); if (defined $problemSeed && $problemSeed =~ /^[+-]?\d+$/) { $problem->problem_seed($problemSeed); - } + } my $visiblityStateClass = ($set->visible) ? "font-visible" : "font-hidden"; my $visiblityStateText = ($set->visible) ? $r->maketext("visible to students")."." : $r->maketext("hidden from students")."."; @@ -540,7 +540,7 @@ sub pre_header_initialize { # test for additional problem validity if it's not already invalid } else { $self->{invalidProblem} = !(defined $problem and ($set->visible || $authz->hasPermissions($userName, "view_hidden_sets"))); - + $self->addbadmessage(CGI::p($r->maketext("This problem will not count towards your grade."))) if $problem and not $problem->value and not $self->{invalidProblem}; } @@ -551,9 +551,9 @@ sub pre_header_initialize { $self->{set} = $set; $self->{problem} = $problem; $self->{editMode} = $editMode; - + ##### form processing ##### - + # set options from form fields (see comment at top of file for names) my $displayMode = $r->param("displayMode") || $user->displayMode || $ce->{pg}->{options}->{displayMode}; my $redisplay = $r->param("redisplay"); @@ -619,9 +619,9 @@ sub pre_header_initialize { showAnsHashInfo => $r->param('showAnsHashInfo') || $ce->{pg}->{options}->{showAnsHashInfo}, showPGInfo => $r->param('showPGInfo') || $ce->{pg}->{options}->{showPGInfo}, showResourceInfo => $r->param('showResourceInfo') || $ce->{pg}->{options}->{showResourceInfo}, - showHints => $r->param("showHints") || $ce->{pg}->{options}{use_knowls_for_hints} + showHints => $r->param("showHints") || $ce->{pg}->{options}{use_knowls_for_hints} || $ce->{pg}->{options}->{showHints}, #set to 0 in defaults.config - showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}{use_knowls_for_solutions} + showSolutions => $r->param("showSolutions") || $ce->{pg}->{options}{use_knowls_for_solutions} || $ce->{pg}->{options}->{showSolutions}, #set to 0 in defaults.config useMathView => $user->useMathView ne '' ? $user->useMathView : $ce->{pg}->{options}->{useMathView}, useWirisEditor => $user->useWirisEditor ne '' ? $user->useWirisEditor : $ce->{pg}->{options}->{useWirisEditor}, @@ -649,7 +649,7 @@ sub pre_header_initialize { useWirisEditor => 0, useMathQuill => 0, ); - + # does the user have permission to use certain options? my @args = ($user, $effectiveUser, $set, $problem); @@ -705,15 +705,15 @@ sub pre_header_initialize { $will{$_} = $can{$_} && ($want{$_} || $must{$_}); #warn "final values for options $_ is can $can{$_}, want $want{$_}, must $must{$_}, will $will{$_}"; } - + ##### sticky answers ##### - + if (not ($submitAnswers or $previewAnswers or $checkAnswers) and $will{showOldAnswers}) { # do this only if new answers are NOT being submitted my %oldAnswers = decodeAnswers($problem->last_answer); $formFields->{$_} = $oldAnswers{$_} foreach keys %oldAnswers; } - + ##### translation ##### debug("begin pg processing"); @@ -760,11 +760,11 @@ sub pre_header_initialize { } ##### update and fix hint/solution options after PG processing ##### - - $can{showHints} &&= $pg->{flags}->{hintExists} + + $can{showHints} &&= $pg->{flags}->{hintExists} &&= $pg->{flags}->{showHintLimit}<=$pg->{state}->{num_of_incorrect_ans}; $can{showSolutions} &&= $pg->{flags}->{solutionExists}; - + ##### record errors ######### if (ref ($pg->{pgcore}) ) { my @debug_messages = @{$pg->{pgcore}->get_debug_messages}; @@ -780,7 +780,7 @@ sub pre_header_initialize { } ##### store fields ##### - + $self->{want} = \%want; $self->{must} = \%must; $self->{can} = \%can; @@ -809,16 +809,16 @@ sub warnings { print CGI::p($r->maketext("Unable to obtain error messages from within the PG question." )); print CGI::end_div(); } elsif ( $self->{pgerrors} > 0 ) { - my @pgdebug = (defined $self->{pgdebug}) ? @{ $self->{pgdebug}} : () ; + my @pgdebug = (defined $self->{pgdebug}) ? @{ $self->{pgdebug}} : () ; my @pgwarning = (defined $self->{pgwarning}) ? @{ $self->{pgwarning}} : (); my @pginternalerrors = (defined $self->{pginternalerrors}) ? @{ $self->{pginternalerrors}} : (); print CGI::start_div(); print CGI::h3({style=>"color:red;"}, $r->maketext("PG question processing error messages")); print CGI::p(CGI::h3($r->maketext("PG debug messages" ) ), join(CGI::br(), @pgdebug ) ) if @pgdebug ; - print CGI::p(CGI::h3($r->maketext("PG warning messages" ) ),join(CGI::br(), @pgwarning) ) if @pgwarning ; + print CGI::p(CGI::h3($r->maketext("PG warning messages" ) ),join(CGI::br(), @pgwarning) ) if @pgwarning ; print CGI::p(CGI::h3($r->maketext("PG internal errors" ) ), join(CGI::br(), @pginternalerrors )) if @pginternalerrors; print CGI::end_div(); - } + } # print "proceeding to SUPER::warnings"; $self->SUPER::warnings(); # print $self->{pgerrors}; @@ -827,7 +827,7 @@ sub warnings { sub if_errors($$) { my ($self, $arg) = @_; - + if ($self->{isOpen}) { return $self->{pg}->{flags}->{error_flag} ? $arg : !$arg; } else { @@ -864,10 +864,10 @@ sub siblings { my $ce = $r->ce; my $authz = $r->authz; my $urlpath = $r->urlpath; - + # can't show sibling problems if the set is invalid return "" if $self->{invalidSet}; - + my $courseID = $urlpath->arg("courseID"); my $setID = $self->{set}->set_id; my $eUserID = $r->param("effectiveUser"); @@ -881,7 +881,7 @@ sub siblings { $isJitarSet = 1; } } - + my @where = map {[$eUserID, $setID, $_]} @problemIDs; my @problemRecords = $db->getMergedProblems(@where); @@ -894,12 +894,12 @@ sub siblings { my $currentProblemID = $self->{problem}->problem_id if !($self->{invalidProblem}); my $progressBarEnabled = $r->ce->{pg}->{options}->{enableProgressBar}; - + print CGI::start_div({class=>"info-box", id=>"fisheye"}); print CGI::h2($r->maketext("Problems")); print CGI::start_ul({class=>"problem-list"}); - + my @items; foreach my $problemID (@problemIDs) { @@ -907,21 +907,21 @@ sub siblings { shift(@problemRecords) if $progressBarEnabled; next; } - + my $problemPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", $r, courseID => $courseID, setID => $setID, problemID => $problemID); my $link; - + my $status_symbol = ''; if($progressBarEnabled){ my $problemRecord = shift(@problemRecords); $num_of_problems++; my $total_attempts = $problemRecord->num_correct+$problemRecord->num_incorrect; - + my $status = $problemRecord->status; if ($isJitarSet) { $status = jitar_problem_adjusted_status($problemRecord,$db); } - + # variables for the widths of the bars in the Progress Bar if( $status ==1 ){ # correct @@ -941,28 +941,28 @@ sub siblings { } } } - + # if its a jitar set we need to hide and disable links to hidden or restricted - # problems. + # problems. if ($isJitarSet) { - + my @seq = jitar_id_to_seq($problemID); my $level = $#seq; my $class = ''; if ($level != 0) { $class='nested-problem-'.$level; } - + if (!$authz->hasPermissions($eUserID, "view_unopened_sets") && is_jitar_problem_closed($db, $ce, $eUserID, $setID, $problemID)) { $link = CGI::a( {href=>'#', class=>$class.' disabled-problem'}, $r->maketext("Problem [_1]", join('.',@seq))); } else { $link = CGI::a( {class=>$class,href=>$self->systemLink($problemPage)}, $r->maketext("Problem [_1]", join('.',@seq)).($progressBarEnabled?$status_symbol:"")); - + } } else { $link = CGI::a( {href=>$self->systemLink($problemPage)}, $r->maketext("Problem [_1]", $problemID).($progressBarEnabled?$status_symbol:"")); } - + push @items, CGI::li({($progressBarEnabled && $currentProblemID eq $problemID ? ('class','currentProblem'):())},$link); } @@ -973,8 +973,8 @@ sub siblings { my $progress_bar_incorrect_width = $total_incorrect*100/$num_of_problems; my $progress_bar_inprogress_width = $total_inprogress*100/$num_of_problems; my $progress_bar_unattempted_width = $unattempted*100/$num_of_problems; - - # construct the progress bar + + # construct the progress bar # CORRECT | IN PROGRESS | INCORRECT | UNATTEMPTED my $progress_bar = CGI::start_div({-class=>"progress-bar set-id-tooltip", "aria-label"=>"progress bar for current problem set", @@ -982,17 +982,17 @@ sub siblings { if($total_correct>0){ $progress_bar .= CGI::div({-class=>"correct-progress set-id-tooltip",-style=>"width:$progress_bar_correct_width%", "aria-label"=>"correct progress bar for current problem set", - "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", + "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", "data-original-title"=>$r->maketext("Correct: [_1]/[_2]",$total_correct,$num_of_problems) }); # perfect scores deserve some stars (★) $progress_bar .= ($total_correct == $num_of_problems)?"★Perfect★":""; $progress_bar .= CGI::end_div(); - } + } if($total_inprogress>0){ $progress_bar .= CGI::div({-class=>"inprogress-progress set-id-tooltip",-style=>"width:$progress_bar_inprogress_width%", "aria-label"=>"in progress bar for current problem set", - "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", + "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", "data-original-title"=>$r->maketext("In progress: [_1]/[_2]",$total_inprogress, $num_of_problems) }); $progress_bar .= CGI::end_div(); @@ -1000,7 +1000,7 @@ sub siblings { if($total_incorrect>0){ $progress_bar .= CGI::div({-class=>"incorrect-progress set-id-tooltip",-style=>"width:$progress_bar_incorrect_width%", "aria-label"=>"incorrect progress bar for current problem set", - "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", + "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", "data-original-title"=>$r->maketext("Incorrect: [_1]/[_2]",$total_incorrect,$num_of_problems) }); $progress_bar .= CGI::end_div(); @@ -1008,14 +1008,14 @@ sub siblings { if($unattempted>0){ $progress_bar .= CGI::div({-class=>"unattempted-progress set-id-tooltip",-style=>"width:$progress_bar_unattempted_width%", "aria-label"=>"unattempted progress bar for current problem set", - "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", + "data-toggle"=>"tooltip", "data-placement"=>"bottom", title=>"", "data-original-title"=>$r->maketext("Unattempted: [_1]/[_2]",$unattempted,$num_of_problems) }); $progress_bar .= CGI::end_div(); } - # close the progress bar div + # close the progress bar div $progress_bar .= CGI::end_div(); - + # output to the screen print $progress_bar; } @@ -1024,7 +1024,7 @@ sub siblings { print CGI::end_ul(); print CGI::end_div(); - + return ""; } @@ -1052,12 +1052,12 @@ sub nav { my ($prevID, $nextID); # for jitar sets finding the next or previous problem, and seeing if it - # is actually open is a bit more of a process. + # is actually open is a bit more of a process. if (!$self->{invalidProblem}) { my @problemIDs = $db->listUserProblems($eUserID, $setID); @problemIDs = sort { $a <=> $b } @problemIDs; - + if ($isJitarSet) { my @processedProblemIDs; @@ -1076,17 +1076,17 @@ sub nav { $prevID = $problemIDs[$curr_index-1] if $curr_index-1 >=0; $nextID = $problemIDs[$curr_index+1] if $curr_index+1 <= $#problemIDs; - $nextID = '' if ($isJitarSet && $nextID - && !$authz->hasPermissions($eUserID, "view_unopened_sets") + $nextID = '' if ($isJitarSet && $nextID + && !$authz->hasPermissions($eUserID, "view_unopened_sets") && is_jitar_problem_closed($db,$ce, $eUserID,$setID,$nextID)); - - + + } - + my @links; if ($prevID) { - my $prevPage = $urlpath->newFromModule(__PACKAGE__, $r, + my $prevPage = $urlpath->newFromModule(__PACKAGE__, $r, courseID => $courseID, setID => $setID, problemID => $prevID); push @links, $r->maketext("Previous Problem"), $r->location . $prevPage->path, $r->maketext("Previous Problem"); } else { @@ -1100,7 +1100,7 @@ sub nav { } if ($nextID) { - my $nextPage = $urlpath->newFromModule(__PACKAGE__, $r, + my $nextPage = $urlpath->newFromModule(__PACKAGE__, $r, courseID => $courseID, setID => $setID, problemID => $nextID); push @links, $r->maketext("Next Problem"), $r->location . $nextPage->path, $r->maketext("Next Problem"); } else { @@ -1123,7 +1123,7 @@ sub path { my $setName = $urlpath->arg("setID") || ''; my $problemNumber = $urlpath->arg("problemID") || ''; my $prettyProblemNumber = $problemNumber; - + if ($setName) { my $set = $r->db->getGlobalSet($setName); if ($set && $set->assignment_type eq 'jitar' && $problemNumber) { @@ -1136,9 +1136,9 @@ sub path { "$setName", $r->location."/$courseName/$setName", "$prettyProblemNumber", $r->location."/$courseName/$setName/$problemNumber", ); - + print $self->pathMacro($args, @path); - + return ""; } @@ -1165,7 +1165,7 @@ sub body { my $problem = $self->{problem}; my $pg = $self->{pg}; - print CGI::p("Entering Problem::body subroutine. + print CGI::p("Entering Problem::body subroutine. This indicates an old style system.template file -- consider upgrading. ", caller(1), ); @@ -1173,9 +1173,9 @@ sub body { unless($valid eq "valid"){ return $valid; } - - - + + + ##### answer processing ##### debug("begin answer processing"); # if answers were submitted: @@ -1296,21 +1296,21 @@ sub output_message{ # processes and prints out the correct link to the editor of the current problem sub output_editorLink{ - + my $self = shift; my $set = $self->{set}; my $problem = $self->{problem}; my $pg = $self->{pg}; - + my $r = $self->r; my $ce = $r->ce; my $authz = $r->authz; my $urlpath = $r->urlpath; my $user = $r->param('user'); - + my $courseName = $urlpath->arg("courseID"); - + # FIXME: move editor link to top, next to problem number. # format as "[edit]" like we're doing with course info file, etc. # add edit link for set as well. @@ -1322,19 +1322,19 @@ sub output_editorLink{ $forced_field = ['sourceFilePath' => $r->param("sourceFilePath")] if ($set->set_id eq 'Undefined_Set'); if ($authz->hasPermissions($user, "modify_problem_sets") and $ce->{showeditors}->{pgproblemeditor1}) { - my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", $r, + my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor", $r, courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); my $editorURL = $self->systemLink($editorPage, params=>$forced_field); $editorLink = CGI::span(CGI::a({href=>$editorURL,target =>'WW_Editor1'}, $r->maketext("Edit1"))); } if ($authz->hasPermissions($user, "modify_problem_sets") and $ce->{showeditors}->{pgproblemeditor2}) { - my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor2", $r, + my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor2", $r, courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); my $editorURL = $self->systemLink($editorPage, params=>$forced_field); $editorLink2 = CGI::span(CGI::a({href=>$editorURL,target =>'WW_Editor2'}, $r->maketext("Edit2"))); } if ($authz->hasPermissions($user, "modify_problem_sets") and $ce->{showeditors}->{pgproblemeditor3}) { - my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor3", $r, + my $editorPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::PGProblemEditor3", $r, courseID => $courseName, setID => $set->set_id, problemID => $problem->problem_id); my $editorURL = $self->systemLink($editorPage, params=>$forced_field); $editorLink3 = CGI::span(CGI::a({href=>$editorURL,target =>'WW_Editor3'}, $r->maketext("Edit3"))); @@ -1371,7 +1371,7 @@ sub output_checkboxes{ my $showSolutionCheckbox = $ce->{pg}->{options}->{show_solution_checkbox}; my $useKnowlsForHints = $ce->{pg}->{options}->{use_knowls_for_hints}; my $useKnowlsForSolutions = $ce->{pg}->{options}->{use_knowls_for_solutions}; - if ($can{showCorrectAnswers} or $can{showAnsGroupInfo} or + if ($can{showCorrectAnswers} or $can{showAnsGroupInfo} or $can{showAnsHashInfo} or $can{showPGInfo} or $can{showResourceInfo} ) { print $r->maketext("Show:")."  "; } @@ -1448,7 +1448,7 @@ sub output_checkboxes{ } )," "; } - + if ($can{showPGInfo}) { print WeBWorK::CGI_labeled_input( -type => "checkbox", @@ -1493,7 +1493,7 @@ sub output_checkboxes{ } } - + if ($can{showSolutions} ) { if ( $showSolutionCheckbox or not $useKnowlsForSolutions ) { # always allow checkbox to display if knowls are not used. print WeBWorK::CGI_labeled_input( @@ -1516,14 +1516,14 @@ sub output_checkboxes{ print CGI::hidden({id=>"showSolutions_id", name => "showSolutions", value=>1}) } } - - if ($can{showCorrectAnswers} or $can{showAnsGroupInfo} or + + if ($can{showCorrectAnswers} or $can{showAnsGroupInfo} or $can{showHints} or $can{showSolutions} or # needed to put buttons on newline $can{showAnsHashInfo} or $can{showPGInfo} or $can{showResourceInfo}) { print CGI::br(); } - + return ""; } @@ -1543,7 +1543,7 @@ sub output_submit_buttons{ my $user = $r->param('user'); my $effectiveUser = $r->param('effectiveUser'); my %showMeAnother = %{ $self->{showMeAnother} }; - + if ($will{requestNewSeed}){ print WeBWorK::CGI_labeled_input(-type=>"submit", -id=>"submitAnswers_id", -input_attr=>{-name=>"requestNewSeed", -value=>$r->maketext("Request New Version"), -onclick=>"this.form.target='_self'"}); return ""; @@ -1569,15 +1569,15 @@ sub output_submit_buttons{ # only output showMeAnother button if we're not on the showMeAnother page my $SMAURL = $self->systemLink($urlpath->newFromModule("WeBWorK::ContentGenerator::ShowMeAnother", $r,courseID => $courseID, setID => $problem->set_id, problemID =>$problem->problem_id)); - print CGI::a({href=>$SMAURL, class=>"set-id-tooltip", "data-toggle"=>"tooltip", "data-placement"=>"right", id=>"SMA_button", title=>"", target=>"_wwsma", + print CGI::a({href=>$SMAURL, class=>"set-id-tooltip", "data-toggle"=>"tooltip", "data-placement"=>"right", id=>"SMA_button", title=>"", target=>"_wwsma", "data-original-title"=>$r->maketext("You can use this feature [quant,_1,more time,more times,as many times as you want] on this problem",($showMeAnother{MaxReps}>=$showMeAnother{Count})?($showMeAnother{MaxReps}-$showMeAnother{Count}):"")}, $r->maketext("Show me another")); } else { # if showMeAnother is available for the course, and for the current problem (but not yet # because the student hasn't tried enough times) then gray it out; otherwise display nothing - # if $showMeAnother{TriesNeeded} is somehow not an integer or if its -2, use the default value + # if $showMeAnother{TriesNeeded} is somehow not an integer or if its -2, use the default value $showMeAnother{TriesNeeded} = $ce->{pg}->{options}->{showMeAnotherDefault} if ($showMeAnother{TriesNeeded} !~ /^[+-]?\d+$/ || $showMeAnother{TriesNeeded} == -2); - + if($ce->{pg}->{options}->{enableShowMeAnother} and $showMeAnother{TriesNeeded} >-1 ){ my $exhausted = ($showMeAnother{Count}>=$showMeAnother{MaxReps} and $showMeAnother{MaxReps}>-1) ? "exhausted" : ""; print CGI::span({class=>"gray_button set-id-tooltip", @@ -1586,7 +1586,7 @@ sub output_submit_buttons{ }, $r->maketext("Show me another [_1]",$exhausted)); } } - + return ""; } @@ -1613,11 +1613,11 @@ sub output_score_summary{ $prEnabled = 0 if ($rerandomizePeriod < 1); # score summary - warn "num_correct =", $problem->num_correct,"num_incorrect=",$problem->num_incorrect + warn "num_correct =", $problem->num_correct,"num_incorrect=",$problem->num_incorrect unless defined($problem->num_correct) and defined($problem->num_incorrect) ; my $attempts = $problem->num_correct + $problem->num_incorrect; #my $attemptsNoun = $attempts != 1 ? $r->maketext("times") : $r->maketext("time"); - + my $prMessage = ""; if ($prEnabled) { my $attempts_before_rr = (defined($will{requestNewSeed}) && $will{requestNewSeed}) ? 0 @@ -1636,7 +1636,7 @@ sub output_score_summary{ my $problem_status = $problem->status || 0; my $lastScore = wwRound(0, $problem_status * 100).'%'; # Round to whole number my $attemptsLeft = $problem->max_attempts - $attempts; - + my $setClosed = 0; my $setClosedMessage; if (before($set->open_date) or after($set->due_date)) { @@ -1665,70 +1665,70 @@ sub output_score_summary{ $r->maketext("You have attempted this problem [quant,_1,time,times].",$attempts), $prMessage, CGI::br(), $submitAnswers ? $r->maketext("You received a score of [_1] for this attempt.",wwRound(0, $pg->{result}->{score} * 100).'%') . CGI::br():'', $problem->attempted - + ? $r->maketext("Your overall recorded score is [_1]. [_2]",$lastScore,$notCountedMessage) . CGI::br() : "", - $setClosed ? $setClosedMessage : $r->maketext("You have [negquant,_1,unlimited attempts,attempt,attempts] remaining.",$attemptsLeft) + $setClosed ? $setClosedMessage : $r->maketext("You have [negquant,_1,unlimited attempts,attempt,attempts] remaining.",$attemptsLeft) ); }else { print $pg->{state}->{state_summary_msg}; } - - #print jitar specific informaton for students. (and notify instructor + + #print jitar specific informaton for students. (and notify instructor # if necessary if ($set->set_id ne 'Undefined_Set' && $set->assignment_type() eq 'jitar') { my @problemIDs = $db->listUserProblems($effectiveUser, $set->set_id); @problemIDs = sort { $a <=> $b } @problemIDs; - - # get some data + + # get some data my @problemSeqs; my $index; - # this sets of an array of the sequence assoicated to the + # this sets of an array of the sequence assoicated to the #problem_id for (my $i=0; $i<=$#problemIDs; $i++) { $index = $i if ($problemIDs[$i] == $problem->problem_id); my @seq = jitar_id_to_seq($problemIDs[$i]); push @problemSeqs, \@seq; } - + my $next_id = $index+1; my @seq = @{$problemSeqs[$index]}; my @children_counts_indexs; my $hasChildren = 0; - + # this does several things. It finds the index of the next problem # at the same level as the current one. It checks to see if there # are any children, and it finds which of those children count - # toward the grade of this problem. - + # toward the grade of this problem. + while ($next_id <= $#problemIDs && scalar(@{$problemSeqs[$index]}) < scalar(@{$problemSeqs[$next_id]})) { - + my $childProblem = $db->getMergedProblem($effectiveUser,$set->set_id, $problemIDs[$next_id]); $hasChildren = 1; push @children_counts_indexs, $next_id if scalar(@{$problemSeqs[$index]}) + 1 == scalar(@{$problemSeqs[$next_id]}) && $childProblem->counts_parent_grade; $next_id++; - } - + } + # print information if this problem has open children and if the grade # for this problem can be replaced by the grades of its children - if ( $hasChildren + if ( $hasChildren && (($problem->att_to_open_children != -1 && $problem->num_incorrect >= $problem->att_to_open_children) || - ($problem->max_attempts != -1 && + ($problem->max_attempts != -1 && $problem->num_incorrect >= $problem->max_attempts))) { print CGI::br().$r->maketext('This problem has open subproblems. You can visit them by using the links to the left or visiting the set page.'); - + if (scalar(@children_counts_indexs) == 1) { print CGI::br().$r->maketext('The grade for this problem is the larger of the score for this problem, or the score of problem [_1].', join('.', @{$problemSeqs[$children_counts_indexs[0]]})); } elsif (scalar(@children_counts_indexs) > 1) { print CGI::br().$r->maketext('The grade for this problem is the larger of the score for this problem, or the weighted average of the problems: [_1].', join(', ', map({join('.', @{$problemSeqs[$_]})} @children_counts_indexs))); } } - - + + # print information if this set has restricted progression and if you need # to finish this problem (and maybe its children) to proceed if ($set->restrict_prob_progression() && - $next_id <= $#problemIDs && + $next_id <= $#problemIDs && is_jitar_problem_closed($db,$ce,$effectiveUser, $set->set_id, $problemIDs[$next_id])) { if ($hasChildren) { print CGI::br().$r->maketext('You will not be able to proceed to problem [_1] until you have completed, or run out of attempts, for this problem and its graded subproblems.',join('.',@{$problemSeqs[$next_id]})); @@ -1737,8 +1737,8 @@ sub output_score_summary{ print CGI::br().$r->maketext('You will not be able to proceed to problem [_1] until you have completed, or run out of attempts, for this problem.',join('.',@{$problemSeqs[$next_id]})); } } - # print information if this problem counts towards the grade of its parent, - # if it doesn't (and its not a top level problem) then its grade doesnt matter. + # print information if this problem counts towards the grade of its parent, + # if it doesn't (and its not a top level problem) then its grade doesnt matter. if ($problem->counts_parent_grade() && scalar(@seq) != 1) { pop @seq; print CGI::br().$r->maketext('The score for this problem can count towards score of problem [_1].',join('.',@seq)); @@ -1746,21 +1746,21 @@ sub output_score_summary{ pop @seq; print CGI::br().$r->maketext('This score for this problem does not count for the score of problem [_1] or for the set.',join('.',@seq)); } - - # if the instructor has set this up, email the instructor a warning message if + + # if the instructor has set this up, email the instructor a warning message if # the student has run out of attempts on a top level problem and all of its children # and didn't get 100% if ($submitAnswers && $set->email_instructor) { my $parentProb = $db->getMergedProblem($effectiveUser,$set->set_id,seq_to_jitar_id($seq[0])); warn("Couldn't find problem $seq[0] from set ".$set->set_id." in the database") unless $parentProb; - + #email instructor with a message if the student didnt finish if (jitar_problem_finished($parentProb,$db) && jitar_problem_adjusted_status($parentProb,$db) != 1) { WeBWorK::ContentGenerator::ProblemUtil::ProblemUtil::jitar_send_warning_email($self,$parentProb); } - - } + + } } print CGI::end_p(); return ""; @@ -1781,18 +1781,18 @@ sub output_misc{ my $user = $r->param('user'); # print CGI::start_div(); -# +# # my $pgdebug = join(CGI::br(), @{$pg->{pgcore}->{DEBUG_messages}} ); # my $pgwarning = join(CGI::br(), @{$pg->{pgcore}->{WARNING_messages}} ); # my $pginternalerrors = join(CGI::br(), @{$pg->{pgcore}->get_internal_debug_messages} ); # my $pgerrordiv = $pgdebug||$pgwarning||$pginternalerrors; # is 1 if any of these are non-empty -# +# # print CGI::p({style=>"color:red;"}, $r->maketext("Checking additional error messages")) if $pgerrordiv ; # print CGI::p($r->maketext("pg debug"),CGI::br(), $pgdebug ) if $pgdebug ; -# print CGI::p($r->maketext("pg warning"),CGI::br(),$pgwarning ) if $pgwarning ; +# print CGI::p($r->maketext("pg warning"),CGI::br(),$pgwarning ) if $pgwarning ; # print CGI::p($r->maketext("pg internal errors"),CGI::br(), $pginternalerrors) if $pginternalerrors; # print CGI::end_div() if $pgerrordiv ; - + # save state for viewOptions print CGI::hidden( -name => "showOldAnswers", @@ -1808,7 +1808,7 @@ sub output_misc{ -value => $self->{editMode}, ) ) if defined($self->{editMode}) and $self->{editMode} eq 'temporaryFile'; - + # this is a security risk -- students can use this to find the source code for the problem my $permissionLevel = $db->getPermissionLevel($user)->permission; @@ -1847,10 +1847,10 @@ sub output_comments{ my $eUserID = $r->param('effectiveUser'); my $displayMode = $self->{displayMode}; my $authz = $r->authz; - - my $userPastAnswerID = $db->latestProblemPastAnswer($courseName, $eUserID, $setID, $problemID); - #if there is a comment then render it and print it + my $userPastAnswerID = $db->latestProblemPastAnswer($courseName, $eUserID, $setID, $problemID); + + #if there is a comment then render it and print it if ($userPastAnswerID) { my $userPastAnswer = $db->getPastAnswer($userPastAnswerID); if ($userPastAnswer->comment_string) { @@ -1872,7 +1872,7 @@ sub output_comments{ } }); MathJax.Hub.Config(["input/Tex","input/AsciiMath","output/HTML-CSS"]); - + MathJax.Hub.Queue([ "Typeset", MathJax.Hub,'answerComment']); EOS @@ -1884,13 +1884,13 @@ EOS # output_summary subroutine -# prints out the summary of the questions that the student has answered +# prints out the summary of the questions that the student has answered # for the current problem, along with available information about correctness sub output_summary{ - + my $self = shift; - + my $editMode = $self->{editMode}; my $problem = $self->{problem}; my $pg = $self->{pg}; @@ -1908,7 +1908,7 @@ sub output_summary{ my $authz = $r->authz; my $user = $r->param('user'); my $effectiveUser = $r->param('effectiveUser'); - + # attempt summary #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. # until after the due date @@ -1916,20 +1916,20 @@ sub output_summary{ if (defined($pg->{flags}->{showPartialCorrectAnswers}) and ($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) { - # print this if user submitted answers OR requested correct answers - my $results = $self->attemptResults($pg, + # print this if user submitted answers OR requested correct answers + my $results = $self->attemptResults($pg, 1, # showAttemptAnswers --display the unformatted submitted answer attempt $will{showCorrectAnswers}, # showCorrectAnswers $pg->{flags}->{showPartialCorrectAnswers}, # showAttemptResults 1, # showSummary 1 # showAttemptPreview - ); + ); print $results; - + } elsif ($will{checkAnswers}) { # print this if user previewed answers print CGI::div({class=>'ResultsWithError'},$r->maketext("ANSWERS ONLY CHECKED -- ANSWERS NOT RECORDED")), CGI::br(); - print $self->attemptResults($pg, + print $self->attemptResults($pg, 1, # showAttemptAnswers $will{showCorrectAnswers}, # showCorrectAnswers 1, # showAttemptResults @@ -1958,35 +1958,35 @@ sub output_summary{ my @problemIDs = $db->listUserProblems($effectiveUser, $set->set_id); @problemIDs = sort { $a <=> $b } @problemIDs; - # get some data + # get some data my @problemSeqs; my $index; - # this sets of an array of the sequence associated to the + # this sets of an array of the sequence associated to the #problem_id for (my $i=0; $i<=$#problemIDs; $i++) { $index = $i if ($problemIDs[$i] == $problem->problem_id); my @seq = jitar_id_to_seq($problemIDs[$i]); push @problemSeqs, \@seq; } - + my $next_id = $index+1; my @seq = @{$problemSeqs[$index]}; - + # check to see if the problem has children while ($next_id <= $#problemIDs && scalar(@{$problemSeqs[$index]}) < scalar(@{$problemSeqs[$next_id]})) { $hasChildren = 1; $next_id++; - } - + } + # if it has children and conditions are right, print a message - if ( $hasChildren + if ( $hasChildren && (($problem->att_to_open_children != -1 && $problem->num_incorrect >= $problem->att_to_open_children) || - ($problem->max_attempts != -1 && + ($problem->max_attempts != -1 && $problem->num_incorrect >= $problem->max_attempts))) { print CGI::div({class=>'showMeAnotherBox'},$r->maketext('This problem has open subproblems. You can visit them by using the links to the left or visiting the set page.')); } - } - + } + if (!$previewAnswers) { # only color answers if not previewing if ($checkAnswers or $showPartialCorrectAnswers) { # color answers when partialCorrectAnswers is set @@ -2008,7 +2008,7 @@ sub output_summary{ sub output_achievement_message{ my $self = shift; - + my $editMode = $self->{editMode}; my $problem = $self->{problem}; my $pg = $self->{pg}; @@ -2021,14 +2021,14 @@ sub output_achievement_message{ my $authz = $r->authz; my $user = $r->param('user'); - + #If achievements enabled, and if we are not in a try it page, check to see if there are new ones.and print them - if ($ce->{achievementsEnabled} && $will{recordAnswers} + if ($ce->{achievementsEnabled} && $will{recordAnswers} && $submitAnswers && $problem->set_id ne 'Undefined_Set') { my $achievementMessage = WeBWorK::AchievementEvaluator::checkForAchievements($problem, $pg, $r); print $achievementMessage; } - + return ""; } @@ -2065,7 +2065,7 @@ sub output_custom_edit_message{ my $user = $r->param('user'); my $editMode = $self->{editMode}; my $problem = $self->{problem}; - + # custom message for editor if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) { if ($editMode eq "temporaryFile") { @@ -2074,7 +2074,7 @@ sub output_custom_edit_message{ # taken care of in the initialization phase } } - + return ""; } @@ -2089,17 +2089,17 @@ sub output_past_answer_button{ my $self = shift; my $r = $self->r; my $problem = $self->{problem}; - + my $authz = $r->authz; my $urlpath = $r->urlpath; my $user = $r->param('user'); - + my $courseName = $urlpath->arg("courseID"); - my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers", $r, + my $pastAnswersPage = $urlpath->newFromModule("WeBWorK::ContentGenerator::Instructor::ShowAnswers", $r, courseID => $courseName); my $showPastAnswersURL = $self->systemLink($pastAnswersPage, authen => 0); # no authen info for form action - + my $problemNumber = $problem->problem_id; my $setRecord = $r->db->getGlobalSet($problem->set_id); if ( defined($setRecord) && $setRecord->assignment_type eq 'jitar' ) { @@ -2122,7 +2122,7 @@ sub output_past_answer_button{ ), "\n", CGI::end_form(); } - + return ""; } @@ -2132,14 +2132,22 @@ sub output_past_answer_button{ sub output_email_instructor{ my $self = shift; + my $r = $self->r; my $problem = $self->{problem}; my %will = %{ $self->{will} }; my $pg = $self->{pg}; + my $userName = $r->param('user'); + my $user = $r->db->getUser($userName); print $self->feedbackMacro( module => __PACKAGE__, set => $self->{set}->set_id, problem => $problem->problem_id, + problemPath => $problem->source_file, + randomSeed => $problem->problem_seed, + notifyAddresses => join(";",$self->fetchEmailRecipients('receive_feedback',$user)), + emailableURL => $self->generateURLs('absolute'), + studentName => $user->full_name, displayMode => $self->{displayMode}, showOldAnswers => $will{showOldAnswers}, showCorrectAnswers => $will{showCorrectAnswers}, @@ -2147,7 +2155,7 @@ sub output_email_instructor{ showSolutions => $will{showSolutions}, pg_object => $pg, ); - + return ""; } @@ -2156,7 +2164,7 @@ sub output_email_instructor{ sub output_hidden_info { my $self = shift; - print CGI::hidden({name => "templateName", + print CGI::hidden({name => "templateName", id=>"templateName_id", value => $self->{templateName}} ); return ""; @@ -2167,22 +2175,22 @@ sub output_hidden_info { # prints out the wz_tooltip.js script for the current site. sub output_wztooltip_JS{ - + my $self = shift; my $r = $self->r; my $ce = $r->ce; my $site_url = $ce->{webworkURLs}->{htdocs}; - + print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/legacy/vendor/wz_tooltip.js"}), CGI::end_script(); return ""; } -# outputs all of the Javascript needed for this page. -# The main javascript needed here is color.js, which colors input fields based on whether or not -# they are correct when answers are submitted. When a problem attempts results, it prints out hidden fields containing identification -# information for the fields that were correct and the fields that were incorrect. color.js collects of the correct and incorrect fields into -# two arrays using the information gathered from the hidden fields, and then loops through and changes the styles so +# outputs all of the Javascript needed for this page. +# The main javascript needed here is color.js, which colors input fields based on whether or not +# they are correct when answers are submitted. When a problem attempts results, it prints out hidden fields containing identification +# information for the fields that were correct and the fields that were incorrect. color.js collects of the correct and incorrect fields into +# two arrays using the information gathered from the hidden fields, and then loops through and changes the styles so # that the colors will show up correctly. sub output_JS{ @@ -2194,20 +2202,20 @@ sub output_JS{ # This adds the dragmath functionality print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/legacy/dragmath.js"}), CGI::end_script(); - + # This file declares a function called addOnLoadEvent which allows multiple different scripts to add to a single onLoadEvent handler on a page. print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/apps/AddOnLoad/addOnLoadEvent.js"}), CGI::end_script(); - + # This is a file which initializes the proper JAVA applets should they be needed for the current problem. print CGI::start_script({type=>"tesxt/javascript", src=>"$site_url/js/legacy/java_init.js"}), CGI::end_script(); - + # The color.js file, which uses javascript to color the input fields based on whether they are correct or incorrect. print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/apps/InputColor/color.js"}), CGI::end_script(); - + # The Base64.js file, which handles base64 encoding and decoding print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/apps/Base64/Base64.js"}), CGI::end_script(); - - # This is for MathView. + + # This is for MathView. if ($self->{will}->{useMathView}) { if ((grep(/MathJax/,@{$ce->{pg}->{displayModes}}))) { print CGI::start_script({type=>"text/javascript", src=>"$ce->{webworkURLs}->{MathJax}"}), CGI::end_script(); @@ -2221,7 +2229,7 @@ sub output_JS{ } } - # WirisEditor + # WirisEditor if ($self->{will}->{useWirisEditor}) { print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/apps/WirisEditor/quizzes.js"}), CGI::end_script(); print CGI::start_script({type=>"text/javascript", src=>"$site_url/js/apps/WirisEditor/wiriseditor.js"}), CGI::end_script(); diff --git a/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm b/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm index 79dcedbfd8..a9fcab4292 100644 --- a/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm +++ b/lib/WeBWorK/ContentGenerator/ProblemUtil/ProblemUtil.pm @@ -448,9 +448,9 @@ sub output_JS{ # prints out summary information for the problem pages. # sub output_summary{ -# +# # my $self = shift; -# +# # my $editMode = $self->{editMode}; # my $problem = $self->{problem}; # my $pg = $self->{pg}; @@ -458,12 +458,12 @@ sub output_JS{ # my %will = %{ $self->{will} }; # my $checkAnswers = $self->{checkAnswers}; # my $previewAnswers = $self->{previewAnswers}; -# +# # my $r = $self->r; -# +# # my $authz = $r->authz; # my $user = $r->param('user'); -# +# # # custom message for editor # if ($authz->hasPermissions($user, "modify_problem_sets") and defined $editMode) { # if ($editMode eq "temporaryFile") { @@ -473,15 +473,15 @@ sub output_JS{ # } # } # print CGI::start_div({class=>"problemHeader"}); -# -# +# +# # # attempt summary # #FIXME -- the following is a kludge: if showPartialCorrectAnswers is negative don't show anything. # # until after the due date # # do I need to check $will{showCorrectAnswers} to make preflight work?? # if (($pg->{flags}->{showPartialCorrectAnswers} >= 0 and $submitAnswers) ) { # # print this if user submitted answers OR requested correct answers -# +# # print $self->attemptResults($pg, 1, # $will{showCorrectAnswers}, # $pg->{flags}->{showPartialCorrectAnswers}, 1, 1); @@ -501,7 +501,7 @@ sub output_JS{ # # don't show attempt results (correctness) # # show attempt previews # } -# +# # print CGI::end_div(); # } @@ -601,6 +601,11 @@ sub output_footer{ module => __PACKAGE__, set => $self->{set}->set_id, problem => $problem->problem_id, + problemPath => $problem->source_file, + randomSeed => $problem->problem_seed, + emailAddress => join(";",$self->fetchEmailRecipients('receive_feedback',$user)), + emailableURL => $self->generateURLs('absolute'), + studentName => $user->full_name, displayMode => $self->{displayMode}, showOldAnswers => $will{showOldAnswers}, showCorrectAnswers => $will{showCorrectAnswers}, @@ -674,21 +679,8 @@ sub jitar_send_warning_email { courseID => $courseID, setID => $setID, problemID => $problemID), params=>{effectiveUser=>$userID}, use_abs_url=>1); - my @recipients; - # send to all users with permission to score_sets an email address - # DBFIXME iterator? - foreach my $rcptName ($db->listUsers()) { - if ($authz->hasPermissions($rcptName, "score_sets")) { - my $rcpt = $db->getUser($rcptName); # checked - next if $ce->{feedback_by_section} and defined $user - and defined $rcpt->section and defined $user->section - and $rcpt->section ne $user->section; - if ($rcpt and $rcpt->email_address) { - # rfc822_mailbox was modified to use RFC 2047 "MIME-Header" encoding. - push @recipients, $rcpt->rfc822_mailbox; - } - } - } + my @recipients = $self->fetchEmailRecipients("score_sets", $user); + # send to all users with permission to score_sets and an email address my $sender; if ($user->email_address) { @@ -732,7 +724,7 @@ sub jitar_send_warning_email { # ssl => $ce->{mail}->{tls_allowed}//1, ## turn on ssl security # timeout => $ce->{mail}->{smtpTimeout} # }); -# +# # createEmailSenderTransportSMTP is defined in ContentGenerator my $transport = $self->createEmailSenderTransportSMTP(); diff --git a/lib/WeBWorK/Utils.pm b/lib/WeBWorK/Utils.pm index 9151921c8c..491d755ebc 100644 --- a/lib/WeBWorK/Utils.pm +++ b/lib/WeBWorK/Utils.pm @@ -117,6 +117,8 @@ our @EXPORT_OK = qw( is_jitar_problem_closed jitar_problem_adjusted_status jitar_problem_finished + fetchEmailRecipients + generateURLs x ); @@ -190,7 +192,7 @@ sub readFile($) { my $fileName = shift; # debugging code: found error in CourseEnvironment.pm with this # if ($fileName =~ /___/ or $fileName =~ /the-course-should-be-determined-at-run-time/) { -# print STDERR "File $fileName not found.\n Usually an unnecessary call to readFile from\n", +# print STDERR "File $fileName not found.\n Usually an unnecessary call to readFile from\n", # join("\t ", caller()), "\n"; # return(); # } @@ -1712,6 +1714,100 @@ sub jitar_problem_finished { return 1; } +# requires a CG object, and a permission type +# a user may also be submitted, in case we need to filter by section +# could require the db, course environment and authz separately... why tho? + +sub fetchEmailRecipients { + my ($self, $permissionType, $sender) = @_; # sender argument is optional + my $r = $self->r; + my $db = $r->db; + my $ce = $r->ce; + my $authz = $r->authz; + my @recipients; + + return unless $permissionType; + + foreach my $potentialRecipient ($db->listUsers()) { + if ($authz->hasPermissions($potentialRecipient, $permissionType)) { + my $validRecipient = $db->getUser($potentialRecipient); + next if $ce->{feedback_by_section} and defined $sender + and defined $validRecipient->section and defined $sender->section + and $validRecipient->section ne $sender->section; + if ($validRecipient and $validRecipient->email_address) { + push @recipients, $validRecipient->rfc822_mailbox; + } + } + } + return @recipients; +} + +# requires a CG object and an optional string +# 'relative' or 'absolute' to return a single URL +# or NULL to return an array containing both URLs +# this subroutine could be expanded to + +sub generateURLs { + my ($self, $urlRequested) = @_; + my $r = $self->r; + my $db = $r->db; + my $urlpath = $r->urlpath; + my $userName = $r->param("user"); + my $setName = $urlpath->arg("setID"); + my $problemNumber = $urlpath->arg("problemID"); + + # generate context URLs + my $emailableURL; + my $returnURL; + if ($userName) { + my $modulePath; + my @args; + if ($setName) { + if ($problemNumber) { + $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::Problem", $r, + courseID => $r->urlpath->arg("courseID"), + setID => $setName, + problemID => $problemNumber, + ); + @args = qw/displayMode showOldAnswers showCorrectAnswers showHints showSolutions/; + } else { + $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSet", $r, + courseID => $r->urlpath->arg("courseID"), + setID => $setName, + ); + @args = (); + } + } else { + $modulePath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", $r, + courseID => $r->urlpath->arg("courseID"), + ); + @args = (); + } + $emailableURL = $self->systemLink($modulePath, + authen => 0, + params => [ "effectiveUser", @args ], + use_abs_url => 1, + ); + $returnURL = $self->systemLink($modulePath, + authen => 1, + params => [ @args ], + ); + } else { + $emailableURL = "(not available)"; + $returnURL = ""; + } + if ($urlRequested) { + if ($urlRequested eq 'relative') { + return $returnURL; + } else { + return $emailableURL; # could include other types of URL here... + } + } else { + return ($emailableURL, $returnURL); + } + return; +} + # This is a dummy function used to mark strings for localization sub x {