--- loncom/interface/lonparmset.pm 2022/05/01 00:57:24 1.611 +++ loncom/interface/lonparmset.pm 2023/12/22 13:38:02 1.621 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set parameters for assessments # -# $Id: lonparmset.pm,v 1.611 2022/05/01 00:57:24 raeburn Exp $ +# $Id: lonparmset.pm,v 1.621 2023/12/22 13:38:02 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -329,6 +329,7 @@ use Apache::lonnavmaps; use Apache::longroup; use Apache::lonrss; use HTML::Entities; +use Text::Wrap(); use LONCAPA qw(:DEFAULT :match); @@ -1252,10 +1253,14 @@ function validateParms() { var dlKeyRegExp = /^deeplink_key_/; var dlMenusRegExp = /^deeplink_menus_/; var dlCollsRegExp = /^deeplink_colls_/; + var dlTargetRegExp = /^deeplink_target_/; + var dlExitRegExp = /^deeplink_exit_/; + var dlExitTextRegExp = /^deeplink_exittext_/; var patternIP = /[\[\]\*\.a-zA-Z\d\-]+/; - if ((document.parmform.elements.length != 'undefined') && (document.parmform.elements.length) != 'null') { - if (document.parmform.elements.length) { - for (i=0; i 0) { + var linktarget = document.parmform.elements[i].options[idx].value + linktarget = linktarget.replace(/^\s+|\s+$/g,''); + if (document.parmform.elements['set_'+identifier].value) { + linktarget = ','+linktarget; + } + document.parmform.elements['set_'+identifier].value += linktarget; + } + } else if (dlExitRegExp.test(name)) { + if (document.parmform.elements[i].checked) { + var identifier = name.replace(dlExitRegExp,''); + var posslinkexit = document.parmform.elements[i].value; + posslinkexit = posslinkexit.replace(/^\s+|\s+$/g,''); + if (document.parmform.elements['set_'+identifier].value) { + posslinkexit = ','+posslinkexit; + } + document.parmform.elements['set_'+identifier].value += posslinkexit; + } + } else if (dlExitTextRegExp.test(name)) { + var identifier = name.replace(dlExitTextRegExp,''); + if ((isRadioSet('deeplink_exit_'+identifier,'yes')) || + (isRadioSet('deeplink_exit_'+identifier,'url'))) { + var posstext = document.parmform.elements[i].value; + 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'); + } + if (posstext !== 'Exit Tool') { + posstext = ':'+posstext; + document.parmform.elements['set_'+identifier].value += posstext; + } + } 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 ,\":;'"); + } else { + alert("An exit link type of 'In use' was selected but the button text value was blank.\nPlease enter the text to use."); + } + return false; + } + } } } } @@ -1550,6 +1604,24 @@ function toggleDeepLink(form,item,key) { keybox.type = 'hidden'; } } + } else if (item == 'exit') { + if (document.getElementById('deeplinkdiv_'+item+'_'+key)) { + if (radios[i].value == 'no') { + document.getElementById('deeplinkdiv_'+item+'_'+key).style.display = 'none'; + if (document.getElementById('deeplink_exittext_'+key)) { + if (document.getElementById('deeplink_exittext_'+key).value != '') { + document.getElementById('deeplink_exittext_'+key).value = ''; + } + } + } else { + document.getElementById('deeplinkdiv_'+item+'_'+key).style.display = 'inline-block'; + if (document.getElementById('deeplink_exittext_'+key)) { + if (document.getElementById('deeplink_exittext_'+key).value == '') { + document.getElementById('deeplink_exittext_'+key).value = 'Exit Tool'; + } + } + } + } } } } @@ -1678,6 +1750,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; @@ -1759,7 +1849,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')) { @@ -2392,6 +2482,7 @@ sub lookUpTableParameter { 'lenient' => 'grading', 'retrypartial' => 'tries', 'discussvote' => 'misc', + 'texdisplay' => 'misc', 'examcode' => 'high_level_randomization', ); } @@ -2481,6 +2572,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 ''); @@ -2494,8 +2587,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(''); @@ -3618,7 +3712,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') { $r->print(''). '" name="recent_'.$item.'" />'); @@ -4379,6 +4473,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}); @@ -4890,7 +4986,7 @@ sub listdata { } if ($is_map) { my $leveltitle = &mt('Folder/Map'); - my $title = &Apache::lonnet::gettitle($1); + my $title = &Apache::lonnet::gettitle($mapurl); if (ref($hash_for_realm) eq 'HASH') { if ($hash_for_realm->{$mapurl} eq '1') { $title = &mt('Main Content'); @@ -4953,15 +5049,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(''); @@ -4994,6 +5101,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)); @@ -5154,9 +5263,13 @@ sub string_ip_selector { sub string_deeplink_selector { my ($thiskey, $showval, $readonly) = @_; - my (@components,%values,@current,%titles,%options,%optiontext,%defaults, - %selectnull,%domlti,%crslti,@possmenus); - @components = ('state','others','listing','scope','protect','menus'); + my (@tables,%values,@current,%titles,%options,%optiontext,%defaults, + %selectnull,%domlti,%crslti,@possmenus,%components); + @tables = ('upper','lower'); + %components = ( + upper => ['state','others','listing','scope'], + lower => ['protect','menus','target','exit'], + ); %titles = &Apache::lonlocal::texthash ( state => 'Access status', others => 'Hide other resources', @@ -5164,6 +5277,8 @@ sub string_deeplink_selector { scope => 'Access scope for link', protect => 'Link protection', menus => 'Menu Items Displayed', + target => 'Embedded?', + exit => 'Exit Tool Button?', ); %options = ( state => ['only','off','both'], @@ -5172,6 +5287,8 @@ sub string_deeplink_selector { scope => ['res','map','rec'], protect => ['none','key','ltid','ltic'], menus => ['std','colls'], + target => ['_self','_top'], + exit => ['no','yes','url'], ); %optiontext = &Apache::lonlocal::texthash ( only => 'deep only', @@ -5193,6 +5310,11 @@ sub string_deeplink_selector { ltid => 'LTI access (domain)' , std => 'Standard (all menus)', colls => 'Numbered collection', + _self => 'Embedded', + _top => 'Not embedded', + no => 'Not in use', + yes => 'In use, no URL redirect', + url => 'In use, redirect to URL', ); %selectnull = &Apache::lonlocal::texthash ( ltic => 'Select Launcher', @@ -5208,6 +5330,8 @@ sub string_deeplink_selector { ($values{'scope'}) = ($current[3] =~ /^(res|map|rec)$/); ($values{'protect'}) = ($current[4] =~ /^(key:[a-zA-Z\d_.!\@#\$%^&*()+=-]+|ltic:\d+|ltid:\d+)$/); ($values{'menus'}) = ($current[5] =~ /^(\d+)$/); + ($values{'target'}) = ($current[6] =~ /^(_self|_top)$/); + ($values{'exit'}) = ($current[7] =~ /^((?:(?:yes|url)(?:|\:[^:;"',]+))|no)$/); } else { $defaults{'state'} = 'off', $defaults{'others'} = 'unhide', @@ -5215,6 +5339,8 @@ sub string_deeplink_selector { $defaults{'scope'} = 'res'; $defaults{'protect'} = 'none'; $defaults{'menus'} = '0'; + $defaults{'target'} = '_top'; + $defaults{'exit'} = 'yes'; } my $disabled; if ($readonly) { @@ -5222,7 +5348,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'}; @@ -5245,119 +5372,144 @@ sub string_deeplink_selector { } } - my $output = ''; - foreach my $item (@components) { - $output .= ''; - } - $output .= ''; - foreach my $item (@components) { - $output .= ''; } - $output .= '
'.$titles{$item}.'
'; - if (($item eq 'protect') || ($item eq 'menus')) { - my $selected = $values{$item}; - foreach my $option (@{$options{$item}}) { - if ($item eq 'protect') { - if ($option eq 'ltid') { - next unless (keys(%domlti)); - } elsif ($option eq 'ltic') { - next unless (keys(%crslti)); - } - } elsif (($item eq 'menus') && ($option eq 'colls')) { - next unless (@possmenus); - } - my $checked; - if ($item eq 'menus') { - if (($selected =~ /^\d+$/) && (@possmenus) && - (grep(/^\Q$selected\E$/,@possmenus))) { - if ($option eq 'colls') { + my $output = ''; + foreach my $table ('upper','lower') { + next unless (ref($components{$table}) eq 'ARRAY'); + $output .= ''; + foreach my $item (@{$components{$table}}) { + $output .= ''; + } + $output .= ''; + foreach my $item (@{$components{$table}}) { + $output .= ''; + } + $output .= '
'.$titles{$item}.'
'; + if (($item eq 'protect') || ($item eq 'menus') || ($item eq 'exit')) { + my $selected = $values{$item}; + foreach my $option (@{$options{$item}}) { + if ($item eq 'protect') { + if ($option eq 'ltid') { + next unless (keys(%domlti)); + } elsif ($option eq 'ltic') { + next unless (keys(%crslti)); + } + } elsif (($item eq 'menus') && ($option eq 'colls')) { + next unless (@possmenus); + } + my $checked; + if ($item eq 'menus') { + if (($selected =~ /^\d+$/) && (@possmenus) && + (grep(/^\Q$selected\E$/,@possmenus))) { + if ($option eq 'colls') { + $checked = ' checked="checked"'; + } + } elsif (($option eq 'std') && ($selected == 0) && ($selected ne '')) { $checked = ' checked="checked"'; } - } elsif (($option eq 'std') && ($selected == 0) && ($selected ne '')) { + } elsif ($selected =~ /^\Q$option\E/) { $checked = ' checked="checked"'; } - } elsif ($selected =~ /^\Q$option\E/) { - $checked = ' checked="checked"'; - } - my $onclick; - unless ($readonly) { - my $esc_key = &js_escape($thiskey); - $onclick = ' onclick="toggleDeepLink(this.form,'."'$item','$esc_key'".');"'; - } - $output .= ''; - if (($item eq 'protect') && ($option eq 'key')) { - my $visibility="hidden"; - my $currkey; - if ($checked) { - $visibility = "text"; - $currkey = (split(/\:/,$values{$item}))[1]; - } - $output .= ' '. - ''; - } elsif (($option eq 'ltic') || ($option eq 'ltid') || ($option eq 'colls')) { - my $display="none"; - my ($current,$blankcheck,@possibles); - if ($checked) { - $display = 'inline-block'; - if (($option eq 'ltic') || ($option eq 'ltid')) { - $current = (split(/\:/,$selected))[1]; + my $onclick; + unless ($readonly) { + my $esc_key = &js_escape($thiskey); + $onclick = ' onclick="toggleDeepLink(this.form,'."'$item','$esc_key'".');"'; + } + $output .= ''; + if (($item eq 'protect') && ($option eq 'key')) { + my $visibility="hidden"; + my $currkey; + if ($checked) { + $visibility = "text"; + $currkey = (split(/\:/,$values{$item}))[1]; + } + $output .= ' '. + ''; + } elsif (($option eq 'ltic') || ($option eq 'ltid') || ($option eq 'colls')) { + my $display="none"; + my ($current,$blankcheck,@possibles); + if ($checked) { + $display = 'inline-block'; + if (($option eq 'ltic') || ($option eq 'ltid')) { + $current = (split(/\:/,$selected))[1]; + } else { + $current = $selected; + } } else { - $current = $selected; - } - } else { - $blankcheck = ' selected="selected"'; - } - if ($option eq 'ltid') { - @possibles = keys(%domlti); - } elsif ($option eq 'ltic') { - @possibles = keys(%crslti); - } else { - @possibles = @possmenus; - } - $output .= '
 '; + if (@possibles > 1) { + $output .= ''."\n"; + } + foreach my $poss (sort { $a <=> $b } @possibles) { + my $selected; + if (($poss == $current) || (scalar(@possibles) ==1)) { + $selected = ' selected="selected"'; + } + my $shown = $poss; + if ($option eq 'ltid') { + $shown = $domlti{$poss}; + } elsif ($option eq 'ltic') { + $shown = $crslti{$poss}; + } + $output .= ''; + } + $output .= '
'; + } + $output .= '
'; + } + if ($item eq 'exit') { + my $exitsty = 'none'; + my $displayval; + if ($values{$item} =~ /^(yes|url)/) { + $exitsty = 'inline-block'; + my $currval = (split(/\:/,$values{$item}))[1]; + if ($currval eq '') { + $displayval = 'Exit Tool'; + } else { + $displayval = $currval; } - $output .= ''; } - $output .= ''; - } - $output .= '
'; - } - } else { - my $selected = $values{$item}; - my $defsel; - if ($selected eq '') { - $defsel = ' selected="selected"'; - } - $output .= ''; } - $output .= '>'.$optiontext{$option}.''; - } - $output .= ''; + } else { + my $selected = $values{$item}; + my $defsel; + if ($selected eq '') { + $defsel = ' selected="selected"'; + } + $output .= ''; + } + $output .= '
'."\n"; + if ($table eq 'upper') { + $output .= '
'; } - $output .= '
'."\n"; return $output; } @@ -5396,7 +5548,10 @@ my %strings = => [['_allowfrom_','Hostname(s), or IP(s) from which access is allowed'], ['_denyfrom_','Hostname(s) or IP(s) from which access is disallowed']], 'string_deeplink' - => [['on','Set choices for link protection, resource listing, access scope, and shown menu items']], + => [['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']], ); @@ -5407,7 +5562,7 @@ my %stringmatches = ( => [['_allowfrom_','[^\!]+'], ['_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+|)$']], + => [['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)(|:[^:;\'",]+)$']], ); my %stringtypes = ( @@ -5418,6 +5573,7 @@ my %stringtypes = ( examcode => 'string_examcode', acc => 'string_ip', deeplink => 'string_deeplink', + texdisplay => 'string_tex', ); # Returns the possible values and titles for a given string type, or undef if there are none. @@ -5478,6 +5634,7 @@ sub string_selector { ($thistype eq 'string_discussvote') || ($thistype eq 'string_ip') || ($thistype eq 'string_deeplink') || + ($thistype eq 'string_tex') || ($name eq 'retrypartial')) { my ($got_chostname,$chostname,$cmajor,$cminor); foreach my $possibilities (@{ $strings{$thistype} }) { @@ -6071,7 +6228,7 @@ ENDOVER $r->print('
'); my $sortorder=$env{'form.sortorder'}; unless ($sortorder) { $sortorder='realmstudent'; } - &sortmenu($r,$sortorder,'newoverview')); + &sortmenu($r,$sortorder,'newoverview'); $r->print('
'); $r->print('

');