--- loncom/interface/lonparmset.pm 2022/07/07 03:37:01 1.616 +++ loncom/interface/lonparmset.pm 2025/07/02 16:33:51 1.626 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set parameters for assessments # -# $Id: lonparmset.pm,v 1.616 2022/07/07 03:37:01 raeburn Exp $ +# $Id: lonparmset.pm,v 1.626 2025/07/02 16:33:51 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -329,6 +329,8 @@ use Apache::lonnavmaps; use Apache::longroup; use Apache::lonrss; use HTML::Entities; +use POSIX qw (floor); +use Text::Wrap(); use LONCAPA qw(:DEFAULT :match); @@ -991,55 +993,46 @@ sub valout { $result=' '; } } else { - if ($type eq 'date_interval') { - my ($totalsecs,$donesuffix) = split(/_/,$value,2); - my ($usesdone,$donebuttontext,$proctor,$secretkey); - if ($donesuffix =~ /^done\:([^\:]+)\:(.*)$/) { - $donebuttontext = $1; - (undef,$proctor,$secretkey) = split(/_/,$2); - $usesdone = 'done'; - } elsif ($donesuffix =~ /^done(|_.+)$/) { - $donebuttontext = &mt('Done'); - ($usesdone,$proctor,$secretkey) = split(/_/,$donesuffix); - } - my ($sec,$min,$hour,$mday,$mon,$year)=gmtime($totalsecs); - my @timer; - $year=$year-70; - $mday--; - if ($year) { -# $result.=&mt('[quant,_1,yr]',$year).' '; - push(@timer,&mt('[quant,_1,yr]',$year)); - } - if ($mon) { -# $result.=&mt('[quant,_1,mth]',$mon).' '; - push(@timer,&mt('[quant,_1,mth]',$mon)); - } - if ($mday) { -# $result.=&mt('[quant,_1,day]',$mday).' '; - push(@timer,&mt('[quant,_1,day]',$mday)); - } - if ($hour) { -# $result.=&mt('[quant,_1,hr]',$hour).' '; - push(@timer,&mt('[quant,_1,hr]',$hour)); - } - if ($min) { -# $result.=&mt('[quant,_1,min]',$min).' '; - push(@timer,&mt('[quant,_1,min]',$min)); - } - if ($sec) { -# $result.=&mt('[quant,_1,sec]',$sec).' '; - push(@timer,&mt('[quant,_1,sec]',$sec)); - } -# $result=~s/\s+$//; - if (!@timer) { # Special case: all entries 0 -> display "0 secs" intead of empty field to keep this field editable - push(@timer,&mt('[quant,_1,sec]',0)); - } - $result.=join(", ",@timer); - if ($usesdone eq 'done') { - if ($secretkey) { - $result .= ' '.&mt('+ "[_1]" with proctor key: [_2]',$donebuttontext,$secretkey); + if (($type eq 'date_interval') || ($type eq 'string_grace')) { + if ($type eq 'string_grace') { + my @items; + if ($value =~ /,/) { + @items = split(/,/,$value); } else { - $result .= ' + "'.$donebuttontext.'"'; + @items = ($value); + } + foreach my $item (@items) { + if ($item =~ /^\d+:(0|1)\.?\d*:(0|1)$/) { + my ($totalsecs,$fraction,$grad) = split(/:/,$item); + $result .= &grace_to_humanstr($totalsecs); + if (($fraction >=0) && ($fraction <=1)) { + $result .= ' | '.$fraction.' '.&mt('pts'); + if ($grad == 1) { + $result .= ' ('.&mt('gradual').')'; + } + } + $result .= ', '; + } + } + $result =~ s/, $//; + } else { + my ($totalsecs,$donesuffix) = split(/_/,$value,2); + $result = &interval_to_humanstr($totalsecs); + my ($usesdone,$donebuttontext,$proctor,$secretkey); + if ($donesuffix =~ /^done\:([^\:]+)\:(.*)$/) { + $donebuttontext = $1; + (undef,$proctor,$secretkey) = split(/_/,$2); + $usesdone = 'done'; + } elsif ($donesuffix =~ /^done(|_.+)$/) { + $donebuttontext = &mt('Done'); + ($usesdone,$proctor,$secretkey) = split(/_/,$donesuffix); + } + if ($usesdone eq 'done') { + if ($secretkey) { + $result .= ' '.&mt('+ "[_1]" with proctor key: [_2]',$donebuttontext,$secretkey); + } else { + $result .= ' + "'.$donebuttontext.'"'; + } } } } elsif (&isdateparm($type)) { @@ -1054,6 +1047,64 @@ sub valout { return $result; } +sub interval_to_humanstr { + my ($totalsecs) = @_; + my ($sec,$min,$hour,$mday,$mon,$year)=gmtime($totalsecs); + my @timer; + $year=$year-70; + $mday--; + if ($year) { + push(@timer,&mt('[quant,_1,yr]',$year)); + } + if ($mon) { + push(@timer,&mt('[quant,_1,mth]',$mon)); + } + if ($mday) { + push(@timer,&mt('[quant,_1,day]',$mday)); + } + if ($hour) { + push(@timer,&mt('[quant,_1,hr]',$hour)); + } + if ($min) { + push(@timer,&mt('[quant,_1,min]',$min)); + } + if ($sec) { + push(@timer,&mt('[quant,_1,sec]',$sec)); + } + if (!@timer) { # Special case: all entries 0 -> display "0 secs" intead of empty field to keep this field editable + push(@timer,&mt('[quant,_1,sec]',0)); + } + return ''.join(', ',@timer).''; +} + +sub grace_to_humanstr { + my ($totalsecs) = @_; + my @timer; + my $weeks = floor($totalsecs/604800); + $totalsecs -= $weeks*604800; + my $days = floor($totalsecs/86400); + $totalsecs -= $days*86400; + my $hours = floor($totalsecs/3600); + $totalsecs -= $hours*3600; + my $mins= floor($totalsecs/60); + $totalsecs -= $mins*60; + if ($weeks) { + push(@timer,&mt('[quant,_1,wk]',$weeks)); + } + if ($days) { + push(@timer,&mt('[quant,_1,day]',$days)); + } + if ($hours) { + push(@timer,&mt('[quant,_1,hr]',$hours)); + } + if ($mins) { + push(@timer,&mt('[quant,_1,min]',$mins)); + } + if (!@timer) { # Special case: all entries 0 -> display "0 mins" intead of empty field to keep this field editable + push(@timer,&mt('[quant,_1,min]',0)); + } + return ''.join(', ',@timer).''; +} # Returns HTML containing a link on a parameter value, for table mode. # The link uses the javascript function 'pjump'. @@ -1234,16 +1285,35 @@ ENDSCRIPT # Javascript function validateParms, for overview mode sub validateparms_js { - return <<'ENDSCRIPT'; + my %lt = &Apache::lonlocal::texthash ( + nodom => "A link type of 'domain LTI launch' was selected but no domain LTI launcher was selected.", + nocrs => "A link type of 'course LTI launch' was selected but no course LTI launcher was selected.", + plss => 'Please select one, or choose a different supported link type.', + disa => 'disallowed character(s) removed from deeplink key.', + nokyr => "A link type of 'deep with key' was selected but the key value was blank, after removing disallowed characters.", + plse => 'Please enter a key using one or more of:', + nokey => "A link type of 'deep with key' was selected but the key value was blank.", + plsk => 'Please enter a key.', + dise => 'disallowed character(s) removed from Exit Button text.', + exit => "An exit link type of 'In use' was selected but the button text value was blank, after removing disallowed characters.", + disc => 'Disallowed characters are ', + notxt => "An exit link type of 'In use' was selected but the button text value was blank.", + plst => 'Please enter the text to use.', + gppc => 'Grace Period Past-Due: enter partial credit (number between 0 and 1.0).', + gpsn => 'Grace Period Past-Due: select a number in at least one of the time past due select boxes, or delete the value for partial credit.', + ); + &js_escape(\%lt); + return <<"ENDSCRIPT"; function validateParms() { var textRegExp = /^settext_/; - var tailLenient = /\.lenient$/; - var patternRelWeight = /^\-?[\d.]+$/; - var patternLenientStd = /^(yes|no|default)$/; + var tailLenient = /\\.lenient\$/; + var patternRelWeight = /^\\-?[\\d.]+\$/; + var patternLenientStd = /^(yes|no|default)\$/; var ipRegExp = /^setip/; var ipallowRegExp = /^setipallow_/; var ipdenyRegExp = /^setipdeny_/; + var graceRegExp = /^setgrace_/; var deeplinkRegExp = /^deeplink_/; var dlListScopeRegExp = /^deeplink_(state|others|listing|scope)_/; var dlLinkProtectRegExp = /^deeplink_protect_/; @@ -1255,7 +1325,8 @@ function validateParms() { var dlTargetRegExp = /^deeplink_target_/; var dlExitRegExp = /^deeplink_exit_/; var dlExitTextRegExp = /^deeplink_exittext_/; - var patternIP = /[\[\]\*\.a-zA-Z\d\-]+/; + var patternIP = /[\\[\\]\\*\\.a-zA-Z\\d\\-]+/; + var patternGrace = /^\\d+:(0|1)\\.?\\d*:(0|1)\$/; var numelements = document.parmform.elements.length; if ((typeof(numelements) != 'undefined') && (numelements != null)) { if (numelements) { @@ -1269,7 +1340,7 @@ function validateParms() { if (document.parmform.elements['set_'+identifier][j].checked) { if (!(patternLenientStd.test(document.parmform.elements['set_'+identifier][j].value))) { var relweight = document.parmform.elements[i].value; - relweight = relweight.replace(/^\s+|\s+$/g,''); + relweight = relweight.replace(/^\s+|\s+\$/g,''); if (!patternRelWeight.test(relweight)) { relweight = '0.0'; } @@ -1288,7 +1359,7 @@ function validateParms() { if (ipallowRegExp.test(name)) { var identifier = name.replace(ipallowRegExp,''); var possallow = document.parmform.elements[i].value; - possallow = possallow.replace(/^\s+|\s+$/g,''); + possallow = possallow.replace(/^\s+|\s+\$/g,''); if (patternIP.test(possallow)) { if (document.parmform.elements['set_'+identifier].value) { possallow = ','+possallow; @@ -1298,7 +1369,7 @@ function validateParms() { } else if (ipdenyRegExp.test(name)) { var identifier = name.replace(ipdenyRegExp,''); var possdeny = document.parmform.elements[i].value; - possdeny = possdeny.replace(/^\s+|\s+$/g,''); + possdeny = possdeny.replace(/^\s+|\s+\$/g,''); if (patternIP.test(possdeny)) { possdeny = '!'+possdeny; if (document.parmform.elements['set_'+identifier].value) { @@ -1313,7 +1384,7 @@ function validateParms() { var idx = document.parmform.elements[i].selectedIndex; if (idx > 0) { var possdeeplink = document.parmform.elements[i].options[idx].value - possdeeplink = possdeeplink.replace(/^\s+|\s+$/g,''); + possdeeplink = possdeeplink.replace(/^\s+|\s+\$/g,''); if (document.parmform.elements['set_'+identifier].value) { possdeeplink = ','+possdeeplink; } @@ -1323,7 +1394,7 @@ function validateParms() { if (document.parmform.elements[i].checked) { var identifier = name.replace(dlLinkProtectRegExp,''); var posslinkurl = document.parmform.elements[i].value; - posslinkurl = posslinkurl.replace(/^\s+|\s+$/g,''); + posslinkurl = posslinkurl.replace(/^\s+|\s+\$/g,''); if (document.parmform.elements['set_'+identifier].value) { posslinkurl = ','+posslinkurl; } @@ -1341,7 +1412,7 @@ function validateParms() { document.parmform.elements['set_'+identifier].value += possltid; } else { document.parmform.elements['set_'+identifier].value = ''; - alert("A link type of 'domain LTI launch' was selected but no domain LTI launcher was selected.\nPlease select one, or choose a different supported link type."); + alert("$lt{'nodom'}\\n$lt{'plss'}"); return false; } } @@ -1357,7 +1428,7 @@ function validateParms() { document.parmform.elements['set_'+identifier].value += possltic; } else { document.parmform.elements['set_'+identifier].value = ''; - alert("A link type of 'course LTI launch' was selected but no course LTI launcher was selected.\nPlease select one, or choose a different supported link type."); + alert("$lt{'nocrs'}\\n$lt{'plss'}"); return false; } } @@ -1365,14 +1436,14 @@ function validateParms() { var identifier = name.replace(dlKeyRegExp,''); if (isRadioSet('deeplink_protect_'+identifier,'key')) { var posskey = document.parmform.elements[i].value; - posskey = posskey.replace(/^\s+|\s+$/g,''); + posskey = posskey.replace(/^\s+|\s+\$/g,''); var origlength = posskey.length; - posskey = posskey.replace(/[^a-zA-Z\d_.!@#$%^&*()+=-]/g,''); + posskey = posskey.replace(/[^a-zA-Z\d_.!\@#\$%^&*()+=-]/g,''); var newlength = posskey.length; if (newlength > 0) { var change = origlength - newlength; if (change) { - alert(change+' disallowed character(s) removed from deeplink key'); + alert(change+" $lt{'disa'}"); } if (document.parmform.elements['set_'+identifier].value) { posskey = ':'+posskey; @@ -1381,9 +1452,9 @@ function validateParms() { } else { document.parmform.elements['set_'+identifier].value = ''; if (newlength < origlength) { - alert("A link type of 'deep with key' was selected but the key value was blank, after removing disallowed characters.\nPlease enter a key using one or more of: a-zA-Z0-9_.!@#$%^&*()+=-"); + alert("$lt{'nokyr'}\\n$lt{'plse'} "+'a-zA-Z0-9_.!\@#\$%^&*()+=-'); } else { - alert("A link type of 'deep with key' was selected but the key value was blank.\nPlease enter a key."); + alert("$lt{'nokey'}\\n$lt{'plsk'}"); } return false; } @@ -1392,7 +1463,7 @@ function validateParms() { if (document.parmform.elements[i].checked) { var identifier = name.replace(dlMenusRegExp,''); var posslinkmenu = document.parmform.elements[i].value; - posslinkmenu = posslinkmenu.replace(/^\s+|\s+$/g,''); + posslinkmenu = posslinkmenu.replace(/^\s+|\s+\$/g,''); if (posslinkmenu == 'std') { posslinkmenu = '0'; if (document.parmform.elements['set_'+identifier].value) { @@ -1415,7 +1486,7 @@ function validateParms() { var idx = document.parmform.elements[i].selectedIndex; if (idx > 0) { var linktarget = document.parmform.elements[i].options[idx].value - linktarget = linktarget.replace(/^\s+|\s+$/g,''); + linktarget = linktarget.replace(/^\s+|\s+\$/g,''); if (document.parmform.elements['set_'+identifier].value) { linktarget = ','+linktarget; } @@ -1425,7 +1496,7 @@ function validateParms() { if (document.parmform.elements[i].checked) { var identifier = name.replace(dlExitRegExp,''); var posslinkexit = document.parmform.elements[i].value; - posslinkexit = posslinkexit.replace(/^\s+|\s+$/g,''); + posslinkexit = posslinkexit.replace(/^\s+|\s+\$/g,''); if (document.parmform.elements['set_'+identifier].value) { posslinkexit = ','+posslinkexit; } @@ -1436,14 +1507,14 @@ function validateParms() { if ((isRadioSet('deeplink_exit_'+identifier,'yes')) || (isRadioSet('deeplink_exit_'+identifier,'url'))) { var posstext = document.parmform.elements[i].value; - posstext = posstext.replace(/^\s+|\s+$/g,''); + posstext = posstext.replace(/^\s+|\s+\$/g,''); var origlength = posstext.length; posstext = posstext.replace(/[:;'",]/g,''); var newlength = posstext.length; if (newlength > 0) { var change = origlength - newlength; if (change) { - alert(change+' disallowed character(s) removed from Exit Button text'); + alert(change+" $lt{'dise'}"); } if (posstext !== 'Exit Tool') { posstext = ':'+posstext; @@ -1452,14 +1523,95 @@ function validateParms() { } else { document.parmform.elements['set_'+identifier].value = ''; if (newlength < origlength) { - alert("An exit link type of 'In use' was selected but the button text value was blank, after removing disallowed characters.\nDisallowed characters are ,\":;'"); + alert("$lt{'exit'}\\n$lt{'disc'}"+'":;\\''); } else { - alert("An exit link type of 'In use' was selected but the button text value was blank.\nPlease enter the text to use."); + alert("$lt{'notxt'}\\n$lt{'plst'}"); } return false; } } } + } else if (graceRegExp.test(name)) { + var identifier = name.replace(graceRegExp,''); + var divElem = document.parmform.elements[i].closest('div'); + var timeSels = divElem.getElementsByTagName("select"); + var total = 0; + var numnotnull = 0; + if (timeSels.length) { + for (var j=0; j 0) && (poss <= 52)) { + total += (poss * 604800); + } + } else if (sname == 'days_'+identifier) { + if ((poss > 0) && (poss <= 6)) { + total += (poss * 86400); + } + } else if (sname == 'hours_'+identifier) { + if ((poss > 0) && (poss < 24)) { + total += (poss * 3600); + } + } else if (sname == 'minutes_'+identifier) { + if ((poss > 0) && (poss < 60)) { + total += (poss * 60); + } + } + } + } + } + if (!numnotnull) { + total = ''; + } + var inputElems = divElem.getElementsByTagName("input"); + var frac = ''; + var grad = ''; + if (inputElems.length) { + for (var j=0; j 0) && (poss <= 1)) { + frac = poss; + numnotnull ++; + } + } + } + } else if (iname == 'grad_'+identifier) { + if (inputElems[j].checked) { + grad = 1; + } else { + grad = 0; + } + } + } + } + if (numnotnull) { + var possgrace = total+':'+frac+':'+grad; + if (patternGrace.test(possgrace)) { + document.parmform.elements[i].value = possgrace; + if (document.parmform.elements['set_'+identifier].value) { + document.parmform.elements['set_'+identifier].value += ','; + } + document.parmform.elements['set_'+identifier].value += document.parmform.elements[i].value; + } else { + if (frac == '') { + alert("$lt{'gppc'}"); + return false; + } else { + alert("$lt{'gpsn'}"); + return false; + } + } + } } } } @@ -1512,6 +1664,47 @@ sub ipacc_boxes_js { END } +sub grace_js { + my %lt = &grace_titles(); + &js_escape(\%lt); + my $overdue = '
'.$lt{'sinc'}.''; + foreach my $which (['weeks', 604800, 52], + ['days', 86400, 6], + ['hours', 3600, 23], + ['minutes', 60, 59]) { + my ($name, $factor, $max) = @{ $which }; + my %select = ((map {$_ => $_} (0..$max)), + 'select_form_order' => [0..$max]); + unshift(@{$select{'select_form_order'}},''); + $select{''} = ''; + my $selector = &Apache::loncommon::select_form('',$name."_'+identifier+'", + \%select); + $selector =~ s/([\r\n\f]+)//g; + $overdue .= $selector.' '.$lt{$name}.(' 'x2).' '; + } + $overdue .= '
'; + return <<"END"; +\$(document).ready(function() { + var wrapper = \$(".LC_string_grace_wrap"); + var add_button = \$(".LC_add_grace_button"); + var graceRegExp = /^LC_string_grace_/; + + \$(add_button).click(function(e){ + e.preventDefault(); + var identifier = \$(this).closest("div").attr("id"); + identifier = identifier.replace(graceRegExp,''); + \$(this).closest('div').find('.LC_string_grace_inner').append('
$overdue
$lt{pcr}  
$lt{remo}
'); + }); + + \$(wrapper).delegate(".LC_remove_grace","click", function(e){ + e.preventDefault(); \$(this).closest("div").remove(); + }) +}); + + +END +} + # Javascript function toggleSecret, for overview mode. sub done_proctor_js { my $defaultdone = &mt('Done'); @@ -1749,6 +1942,24 @@ sub print_row { if ($automatic) { $parm.='
'.&mt('Automatically sets').' '.join(', ',split(/\:/,$automatic)).'
'; } + my $advice; + if ((ref($name) eq 'HASH') && ($name->{$which} eq 'mapalias') && + (ref($symbp) eq 'HASH') && ($parmlev eq 'full')) { + if ($symbp->{$rid} =~ m{^uploaded/}) { + if ($result == 14) { + $advice = &mt('Use Course Editor to modify this.'); + } else { + $advice = &mt('Use Course Editor to set this.'); + } + } else { + if ($result == 14) { + $advice = &mt('Use Resource Assembly Tool to modify this.'); + } else { + $advice = &mt('Use Resource Assembly Tool to set this.'); + } + } + $parm .= '
'.$advice.''; + } $r->print(''.$parm.''); my $thismarker=$which; @@ -1830,7 +2041,7 @@ sub print_row { $extra = 'ltid_'.$domltistr; } } - my %courselti = &Apache::lonnet::get_course_lti($cnum,$cdom); + my %courselti = &Apache::lonnet::get_course_lti($cnum,$cdom,'provider'); if (keys(%courselti)) { foreach my $item (sort { $a <=> $b } keys(%courselti)) { if (($item =~ /^\d+$/) && (ref($courselti{$item}) eq 'HASH')) { @@ -2422,6 +2633,7 @@ sub lookUpTableParameter { 'opendate' => 'time_settings', 'duedate' => 'time_settings', 'answerdate' => 'time_settings', + 'grace' => 'time_settings', 'interval' => 'time_settings', 'contentopen' => 'time_settings', 'contentclose' => 'time_settings', @@ -2463,6 +2675,7 @@ sub lookUpTableParameter { 'lenient' => 'grading', 'retrypartial' => 'tries', 'discussvote' => 'misc', + 'texdisplay' => 'misc', 'examcode' => 'high_level_randomization', ); } @@ -2552,6 +2765,8 @@ sub parmboxes { &whatIsMyCategory($tempparameter, \%categoryList); } #part to print the parm-list + $Text::Wrap::columns=60; + $Text::Wrap::separator='
'; foreach my $key (sort { $category_order{$a} <=> $category_order{$b} } keys(%categoryList)) { next if (@{$categoryList{$key}} == 0); next if ($key eq ''); @@ -2565,8 +2780,9 @@ sub parmboxes { if ($$pscat[0] eq "all" || grep $_ eq $tempkey, @{$pscat}) { $r->print( ' checked="checked"'); } - $r->print(' />'.($$allparms{$tempkey}=~/\S/ ? $$allparms{$tempkey} - : $tempkey) + $r->print(' />'.($$allparms{$tempkey}=~/\S/ ? + Text::Wrap::wrap('',' 'x4,$$allparms{$tempkey}) + : $tempkey) .'
'."\n"); } $r->print(''); @@ -3157,26 +3373,27 @@ sub standardkeyorder { return ('parameter_0_opendate' => 1, 'parameter_0_duedate' => 2, 'parameter_0_answerdate' => 3, - 'parameter_0_interval' => 4, - 'parameter_0_weight' => 5, - 'parameter_0_maxtries' => 6, - 'parameter_0_hinttries' => 7, - 'parameter_0_contentopen' => 8, - 'parameter_0_contentclose' => 9, - 'parameter_0_type' => 10, - 'parameter_0_problemstatus' => 11, - 'parameter_0_hiddenresource' => 12, - 'parameter_0_hiddenparts' => 13, - 'parameter_0_display' => 14, - 'parameter_0_ordered' => 15, - 'parameter_0_tol' => 16, - 'parameter_0_sig' => 17, - 'parameter_0_turnoffunit' => 18, - 'parameter_0_discussend' => 19, - 'parameter_0_discusshide' => 20, - 'parameter_0_discussvote' => 21, - 'parameter_0_printstartdate' => 22, - 'parameter_0_printenddate' => 23); + 'parameter_0_grace' => 4, + 'parameter_0_interval' => 5, + 'parameter_0_weight' => 6, + 'parameter_0_maxtries' => 7, + 'parameter_0_hinttries' => 8, + 'parameter_0_contentopen' => 9, + 'parameter_0_contentclose' => 10, + 'parameter_0_type' => 11, + 'parameter_0_problemstatus' => 12, + 'parameter_0_hiddenresource' => 13, + 'parameter_0_hiddenparts' => 14, + 'parameter_0_display' => 15, + 'parameter_0_ordered' => 16, + 'parameter_0_tol' => 17, + 'parameter_0_sig' => 18, + 'parameter_0_turnoffunit' => 19, + 'parameter_0_discussend' => 20, + 'parameter_0_discusshide' => 21, + 'parameter_0_discussvote' => 22, + 'parameter_0_printstartdate' => 23, + 'parameter_0_printenddate' => 24); } @@ -3689,7 +3906,7 @@ sub assessparms { 'date_interval','int','float','string','string_lenient', 'string_examcode','string_deeplink','string_discussvote', 'string_useslots','string_problemstatus','string_ip', - 'string_questiontype') { + 'string_questiontype','string_tex','string_grace') { $r->print(''). '" name="recent_'.$item.'" />'); @@ -4358,7 +4575,7 @@ sub readdata { # Stores parameter data, using form parameters directly. # # Uses the following form parameters. The variable part in the names is a resourcedata key (except for a modification for user data). -# set_* (except settext, setipallow, setipdeny, setdeeplink) - set a parameter value +# set_* (except settext, setipallow, setipdeny, setdeeplink, setgrace) - set a parameter value # del_* - remove a parameter # datepointer_* - set a date parameter (value is key_* refering to a set of other form parameters) # dateinterval_* - set a date interval parameter (value refers to more form parameters) @@ -4391,7 +4608,7 @@ sub storedata { my $cmd=$1; my $thiskey=$2; my ($altkey,$recursive,$tkey,$tkeyrec,$tkeynonrec); - next if ($cmd eq 'rec' || $cmd eq 'settext' || $cmd eq 'setipallow' || $cmd eq 'setipdeny' || $cmd eq 'setdeeplink'); + next if ($cmd eq 'rec' || $cmd eq 'settext' || $cmd eq 'setipallow' || $cmd eq 'setipdeny' || $cmd eq 'setdeeplink' || $cmd eq 'setgrace'); if ((($cmd eq 'set') || ($cmd eq 'datepointer') || ($cmd eq 'dateinterval') || ($cmd eq 'del')) && ($thiskey =~ /(?:sequence|page)\Q___(all)\E/)) { unless ($thiskey =~ /(encrypturl|hiddenresource)$/) { @@ -4450,6 +4667,8 @@ sub storedata { if ($thiskey =~ /\.retrypartial$/) { $name = 'retrypartial'; } + } elsif ($typeof eq 'string_tex') { + $name = 'texdisplay'; } } elsif ($cmd eq 'datepointer') { $data=&Apache::lonhtmlcommon::get_date_from_form($env{$key}); @@ -5024,15 +5243,26 @@ sub listdata { # Ready to print # my $parmitem = &standard_parameter_names($name); + my $advice; + if (($name eq 'mapalias') && ($middle) && (!$is_map)) { + if ($middle =~ m{^uploaded/}) { + $advice = &mt('Use Course Editor to set this.'); + } else { + $advice = &mt('Use Resource Assembly Tool to set this.'); + } + $advice = '
'.$advice.''; + } $r->print(&tablestart($readonly,$is_map). &Apache::loncommon::start_data_table_row(). ''.&mt($parmitem). - ''); + ''.$advice.''); unless ($readonly) { my $disabled; if (($name eq 'availablestudent') && (($showval eq '') || ($userscope))) { $disabled = ' disabled="disabled"'; + } elsif (($name eq 'mapalias') && ($showval eq '')) { + $disabled = ' disabled="disabled"'; } $r->print(''); @@ -5065,6 +5295,8 @@ sub listdata { } elsif ($thistype =~ m/^string/) { if ($name eq 'availablestudent') { $readonly = 1; + } elsif (($name eq 'mapalias') && ($showval eq '')) { + $readonly = 1; } $r->print(&string_selector($thistype,$thiskey, $showval,$name,$readonly)); @@ -5141,7 +5373,6 @@ sub get_date_interval_from_form { return $seconds; } - # Returns HTML to enter a text value for a parameter. # # @param {string} $thiskey - parameter key @@ -5310,7 +5541,8 @@ sub string_deeplink_selector { } my %courselti = &Apache::lonnet::get_course_lti($env{'course.'.$env{'request.course.id'}.'.num'}, - $env{'course.'.$env{'request.course.id'}.'.domain'}); + $env{'course.'.$env{'request.course.id'}.'.domain'}, + 'provider'); foreach my $item (keys(%courselti)) { if (ref($courselti{$item}) eq 'HASH') { $crslti{$item} = $courselti{$item}{'name'}; @@ -5474,6 +5706,95 @@ sub string_deeplink_selector { return $output; } +sub string_grace_selector { + my ($thiskey, $showval, $readonly) = @_; + my $addmore; + unless ($readonly) { + $addmore = "\n".''; + } + my $output = ''. + '
'."\n". + '
'."\n"; + if ($showval ne '') { + my @current; + if ($showval =~ /,/) { + @current = split(/,/,$showval); + } else { + @current = ($showval); + } + my $num = scalar(@current); + foreach my $item (@current) { + my ($delta,$fraction,$gradational) = split(/:/,$item); + if (($delta =~ /^\d+$/) && ($fraction =~ /^(0|1)\.?\d*$/) && + (($gradational eq 1) || ($gradational eq '0'))) { + my $gradchk = ''; + if ($gradational) { + $gradchk = ' checked="checked"'; + } + $output .= &grace_form($thiskey,$delta,$fraction,$gradchk, + $readonly); + } + } + } elsif (!$readonly) { + $output .= &grace_form($thiskey,'','','',$readonly); + } + $output .= '
'.$addmore.'
'; + return $output; +} + +sub grace_form { + my ($thiskey,$delta,$fraction,$gradchkon,$readonly) = @_; + my $disabled; + if ($readonly) { + $disabled = ' disabled="disabled"'; + } + my %lt = &grace_titles(); + my $output = '
'. + '
'.$lt{'sinc'}.''; + foreach my $which (['weeks', 604800, 52], + ['days', 86400, 6], + ['hours', 3600, 23], + ['minutes', 60, 59]) { + my ($name, $factor, $max) = @{ $which }; + my $amount; + my %select = ((map {$_ => $_} (0..$max)), + 'select_form_order' => [0..$max]); + if ($delta eq '') { + unshift(@{$select{'select_form_order'}},''); + $select{''} = ''; + $amount = ''; + } else { + $amount = int($delta/$factor); + $delta %= $factor; + } + $output .= &Apache::loncommon::select_form($amount,$name.'_'.$thiskey, + \%select,'',$readonly); + $output .= ' '.$lt{$name}.'   '; + } + $output .= '
'. + '
'.$lt{'pcr'}.''. + ''. + '  
'; + unless ($readonly) { + $output .= ''.$lt{'remo'}.''; + } + $output .= '
'."\n"; + return $output; +} + +sub grace_titles { + return &Apache::lonlocal::texthash ( + sinc => 'Time past due', + remo => 'Remove', + pcr => 'Partial credit', + grad => 'gradual', + weeks => 'weeks', + days => 'days', + hours => 'hours', + minutes => 'minutes', + ); +} { # block using some constants related to parameter types (overview mode) @@ -5510,6 +5831,11 @@ my %strings = ['_denyfrom_','Hostname(s) or IP(s) from which access is disallowed']], 'string_deeplink' => [['on','Set choices for link protection, resource listing, access scope, shown menu items, embedding, and exit link']], + 'string_tex' + => [['tth', 'tth (TeX to HTML)'], + ['mathjax', 'MathJax']], + 'string_grace' + => [['on','Set grading scale and grace period for submissions after due date']], ); @@ -5521,6 +5847,8 @@ my %stringmatches = ( ['_denyfrom_','\!']], 'string_deeplink' => [['on','^(only|off|both)\,(hide|unhide)\,(full|absent|grades|details|datestatus)\,(res|map|rec)\,(none|key\:\w+|ltic\:\d+|ltid\:\d+)\,(\d+|)\,_(self|top),(yes|url|no)(|:[^:;\'",]+)$']], + 'string_grace' + => [['on','^\d+,(0|1)\.?\d*,(0|1)']], ); my %stringtypes = ( @@ -5531,6 +5859,8 @@ my %stringtypes = ( examcode => 'string_examcode', acc => 'string_ip', deeplink => 'string_deeplink', + grace => 'string_grace', + texdisplay => 'string_tex', ); # Returns the possible values and titles for a given string type, or undef if there are none. @@ -5591,6 +5921,8 @@ sub string_selector { ($thistype eq 'string_discussvote') || ($thistype eq 'string_ip') || ($thistype eq 'string_deeplink') || + ($thistype eq 'string_tex') || + ($thistype eq 'string_grace') || ($name eq 'retrypartial')) { my ($got_chostname,$chostname,$cmajor,$cminor); foreach my $possibilities (@{ $strings{$thistype} }) { @@ -5628,7 +5960,9 @@ sub string_selector { } if ($thistype eq 'string_ip') { - return &string_ip_selector($thiskey,$showval,$readonly); + return &string_ip_selector($thiskey,$showval,$readonly); + } elsif ($thistype eq 'string_grace') { + return &string_grace_selector($thiskey,$showval,$readonly); } elsif ($thistype eq 'string_deeplink') { return &string_deeplink_selector($thiskey,$showval,$readonly); } @@ -6067,6 +6401,7 @@ sub newoverview { &toggleparmtextbox_js()."\n". &validateparms_js()."\n". &ipacc_boxes_js()."\n". + &grace_js()."\n". &done_proctor_js()."\n". &deeplink_js()."\n". '// ]]> @@ -6079,7 +6414,8 @@ sub newoverview { $r->print($start_page.$breadcrumbs); &startSettingsScreen($r,'parmset',$crstype); $r->print(< +
+ ENDOVER my @ids=(); my %typep=(); @@ -6187,7 +6523,7 @@ ENDOVER &sortmenu($r,$sortorder,'newoverview'); $r->print(''); - $r->print('

'); + $r->print('

'); # Build the list data hash from the specified parms @@ -6199,9 +6535,10 @@ ENDOVER &secgroup_lister($cat,$pschp,$parmlev,$listdata,\@psprt,\@selected_groups,\%defkeytype,\%allmaps,\@ids,\%symbp); } - if (($env{'form.store'}) || ($env{'form.dis'})) { + my $foundkeys; + if ($env{'form.newoverviewsubm'}) { - if ($env{'form.store'}) { &storedata($r,$crs,$dom); } + if ($env{'form.newoverviewsubm'} eq 'store') { &storedata($r,$crs,$dom); } # Read modified data @@ -6217,13 +6554,76 @@ ENDOVER $hash_for_realm->{$symbp{$ids[$i]}} = $i; } } - &listdata($r,$resourcedata,$listdata,$sortorder,'newoverview',undef,$readonly,$parmlev,$hash_for_realm,$pschp); + $foundkeys = &listdata($r,$resourcedata,$listdata,$sortorder,'newoverview',undef,$readonly,$parmlev,$hash_for_realm,$pschp); } $r->print(&tableend()); - unless ($readonly) { - $r->print( ((($env{'form.store'}) || ($env{'form.dis'}))?'

':'') ); + if ((!$readonly) && ($foundkeys)) { + $r->print( ($env{'form.newoverviewsubm'}? '

':'') ); } $r->print('
'); + if ($env{'form.newoverviewsubm'}) { + $r->print(<<"END"); + + +END + } &endSettingsScreen($r); $r->print(&Apache::loncommon::end_page()); } @@ -6295,6 +6695,7 @@ sub overview { &toggleparmtextbox_js()."\n". &validateparms_js()."\n". &ipacc_boxes_js()."\n". + &grace_js()."\n". &done_proctor_js()."\n". &deeplink_js()."\n". '// ]]>'."\n". @@ -7584,6 +7985,9 @@ sub parm_change_log { } else { if (&isdateparm($istype{$parmname})) { $showvalue = &Apache::lonlocal::locallocaltime($value); + } elsif (($istype{$parmname} eq 'string_grace') || + ($istype{$parmname} eq 'string_ip')) { + $showvalue =~ s/,/, /g; } } $output .= $showvalue;