--- loncom/interface/lonparmset.pm 2020/02/10 02:11:46 1.594 +++ loncom/interface/lonparmset.pm 2020/10/29 23:24:13 1.597 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set parameters for assessments # -# $Id: lonparmset.pm,v 1.594 2020/02/10 02:11:46 raeburn Exp $ +# $Id: lonparmset.pm,v 1.597 2020/10/29 23:24:13 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -1241,12 +1241,16 @@ function validateParms() { var tailLenient = /\.lenient$/; var patternRelWeight = /^\-?[\d.]+$/; var patternLenientStd = /^(yes|no|default)$/; + var ipRegExp = /^setip/; var ipallowRegExp = /^setipallow_/; var ipdenyRegExp = /^setipdeny_/; - var deeplinkRegExp = /^deeplink_(listing|scope)_/; - var deeplinkUrlsRegExp = /^deeplink_urls_/; - var deeplinkltiRegExp = /^deeplink_lti_/; - var deeplinkkeyRegExp = /^deeplink_key_/; + var deeplinkRegExp = /^deeplink_/; + var dlListScopeRegExp = /^deeplink_(listing|scope)_/; + var dlLinkUrlsRegExp = /^deeplink_urls_/; + var dlLtiRegExp = /^deeplink_lti_/; + var dlKeyRegExp = /^deeplink_key_/; + var dlMenusRegExp = /^deeplink_menus_/; + var dlCollsRegExp = /^deeplink_colls_/; var patternIP = /[\[\]\*\.a-zA-Z\d\-]+/; if ((document.parmform.elements.length != 'undefined') && (document.parmform.elements.length) != 'null') { if (document.parmform.elements.length) { @@ -1275,61 +1279,117 @@ function validateParms() { } } } - } else if (ipallowRegExp.test(name)) { - var identifier = name.replace(ipallowRegExp,''); - var possallow = document.parmform.elements[i].value; - possallow = possallow.replace(/^\s+|\s+$/g,''); - if (patternIP.test(possallow)) { - if (document.parmform.elements['set_'+identifier].value) { - possallow = ','+possallow; - } - document.parmform.elements['set_'+identifier].value += possallow; - } - } else if (ipdenyRegExp.test(name)) { - var identifier = name.replace(ipdenyRegExp,''); - var possdeny = document.parmform.elements[i].value; - possdeny = possdeny.replace(/^\s+|\s+$/g,''); - if (patternIP.test(possdeny)) { - possdeny = '!'+possdeny; - if (document.parmform.elements['set_'+identifier].value) { - possdeny = ','+possdeny; + } else if (ipRegExp.test(name)) { + if (ipallowRegExp.test(name)) { + var identifier = name.replace(ipallowRegExp,''); + var possallow = document.parmform.elements[i].value; + possallow = possallow.replace(/^\s+|\s+$/g,''); + if (patternIP.test(possallow)) { + if (document.parmform.elements['set_'+identifier].value) { + possallow = ','+possallow; + } + document.parmform.elements['set_'+identifier].value += possallow; + } + } else if (ipdenyRegExp.test(name)) { + var identifier = name.replace(ipdenyRegExp,''); + var possdeny = document.parmform.elements[i].value; + possdeny = possdeny.replace(/^\s+|\s+$/g,''); + if (patternIP.test(possdeny)) { + possdeny = '!'+possdeny; + if (document.parmform.elements['set_'+identifier].value) { + possdeny = ','+possdeny; + } + document.parmform.elements['set_'+identifier].value += possdeny; } - document.parmform.elements['set_'+identifier].value += possdeny; } } else if (deeplinkRegExp.test(name)) { - var identifier = name.replace(deeplinkRegExp,''); - var possdeeplink = document.parmform.elements[i].value; - possdeeplink = possdeeplink.replace(/^\s+|\s+$/g,''); - if (document.parmform.elements['set_'+identifier].value) { - possdeeplink = ','+possdeeplink; - } - document.parmform.elements['set_'+identifier].value += possdeeplink; - } else if (deeplinkUrlsRegExp.test(name)) { - if (document.parmform.elements[i].checked) { - var identifier = name.replace(deeplinkUrlsRegExp,''); - var posslinkurl = document.parmform.elements[i].value; - posslinkurl = posslinkurl.replace(/^\s+|\s+$/g,''); - if (document.parmform.elements['set_'+identifier].value) { - posslinkurl = ','+posslinkurl; - } - document.parmform.elements['set_'+identifier].value += posslinkurl; - } - } else if (deeplinkltiRegExp.test(name)) { - var identifier = name.replace(deeplinkltiRegExp,''); - var posslti = document.parmform.elements[i].value; - posslti = posslti.replace(/\D+/g,''); - if (document.parmform.elements['set_'+identifier].value) { - posslti = ':'+posslti; - } - document.parmform.elements['set_'+identifier].value += posslti; - } else if (deeplinkkeyRegExp.test(name)) { - var identifier = name.replace(deeplinkkeyRegExp,''); - var posskey = document.parmform.elements[i].value; - posskey = posskey.replace(/\W+/g,''); - if (document.parmform.elements['set_'+identifier].value) { - posslti = ':'+posskey; + if (dlListScopeRegExp.test(name)) { + var identifier = name.replace(dlListScopeRegExp,''); + 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,''); + if (document.parmform.elements['set_'+identifier].value) { + possdeeplink = ','+possdeeplink; + } + document.parmform.elements['set_'+identifier].value += possdeeplink; + } + } else if (dlLinkUrlsRegExp.test(name)) { + if (document.parmform.elements[i].checked) { + var identifier = name.replace(dlLinkUrlsRegExp,''); + var posslinkurl = document.parmform.elements[i].value; + posslinkurl = posslinkurl.replace(/^\s+|\s+$/g,''); + if (document.parmform.elements['set_'+identifier].value) { + posslinkurl = ','+posslinkurl; + } + document.parmform.elements['set_'+identifier].value += posslinkurl; + } + } else if (dlLtiRegExp.test(name)) { + var identifier = name.replace(dlLtiRegExp,''); + if (isRadioSet('deeplink_urls_'+identifier,'lti')) { + var posslti = document.parmform.elements[i].value; + posslti = posslti.replace(/\D+/g,''); + if (posslti.length) { + if (document.parmform.elements['set_'+identifier].value) { + posslti = ':'+posslti; + } + document.parmform.elements['set_'+identifier].value += posslti; + } else { + document.parmform.elements['set_'+identifier].value = ''; + alert("A link type of 'deep with LTI launch' was selected but no LTI launcher was selected.\nPlease select one, or choose a different supported link type."); + return false; + } + } + } else if (dlKeyRegExp.test(name)) { + var identifier = name.replace(dlKeyRegExp,''); + if (isRadioSet('deeplink_urls_'+identifier,'key')) { + var posskey = document.parmform.elements[i].value; + posskey = posskey.replace(/^\s+|\s+$/g,''); + var origlength = posskey.length; + 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'); + } + if (document.parmform.elements['set_'+identifier].value) { + posskey = ':'+posskey; + } + document.parmform.elements['set_'+identifier].value += posskey; + } 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_.!@#$%^&*()+=-"); + } else { + alert("A link type of 'deep with key' was selected but the key value was blank.\nPlease enter a key."); + } + return false; + } + } + } else if (dlMenusRegExp.test(name)) { + if (document.parmform.elements[i].checked) { + var identifier = name.replace(dlMenusRegExp,''); + var posslinkmenu = document.parmform.elements[i].value; + posslinkmenu = posslinkmenu.replace(/^\s+|\s+$/g,''); + if (posslinkmenu == 'std') { + posslinkmenu = '0'; + if (document.parmform.elements['set_'+identifier].value) { + posslinkmenu = ','+posslinkmenu; + } + document.parmform.elements['set_'+identifier].value += posslinkmenu; + } + } + } else if (dlCollsRegExp.test(name)) { + var identifier = name.replace(dlCollsRegExp,''); + if (isRadioSet('deeplink_menus_'+identifier,'colls')) { + var posslinkmenu = document.parmform.elements[i].value; + if (document.parmform.elements['set_'+identifier].value) { + posslinkmenu = ','+posslinkmenu; + } + document.parmform.elements['set_'+identifier].value += posslinkmenu; + } } - document.parmform.elements['set_'+identifier].value += posskey; } } } @@ -1337,6 +1397,23 @@ function validateParms() { return true; } +function isRadioSet(name,expected) { + var menuitems = document.getElementsByName(name); + var radioLength = menuitems.length; + result = false; + if (radioLength > 1) { + for (var j=0; j $b } keys(%posslti)) { - $extra .= $lti.':'.&js_escape($posslti{$lti}).','; + $extra .= $lti.':'.&escape($posslti{$lti}).','; } $extra =~ s/,$//; } + if ($env{'course.'.$env{'request.course.id'}.'.menucollections'}) { + my @colls; + foreach my $item (split(/;/,$env{'course.'.$env{'request.course.id'}.'.menucollections'})) { + my ($num,$value) = split(/\%/,$item); + if ($num =~ /^\d+$/) { + push(@colls,$num); + } + } + if (@colls) { + if ($extra) { + $extra .= '&'; + } + $extra .= 'menus_'.join(',',@colls); + } + } } if ($parmlev eq 'general') { if ($uname) { @@ -1766,7 +1870,7 @@ sub print_row { # @param {boolean} $noeditgrp - true if no edit is allowed for group level parameters # @param {boolean} $readonly -true if editing not allowed. # @param {boolean} $ismaplevel - true if level is for a map. -# @param {strring} $extra - extra informatio to pass to plink. +# @param {string} $extra - extra information to pass to plink. sub print_td { my ($r,$which,$defbg,$result,$outpar,$mprefix,$value,$typeoutpar,$display, $noeditgrp,$readonly,$ismaplevel,$extra)=@_; @@ -2420,9 +2524,37 @@ sub partmenu { sub usermenu { my ($r,$uname,$id,$udom,$csec,$cgroup,$parmlev,$usersgroups,$pssymb)=@_; my $chooseopt=&Apache::loncommon::select_dom_form($udom,'udom').' '. - &Apache::loncommon::selectstudent_link('parmform','uname','udom'); - my $selscript=&Apache::loncommon::studentbrowser_javascript(); + &Apache::loncommon::selectstudent_link('parmform','uname','udom','condition'). + &Apache::lonhtmlcommon::scripttag(<'. + $stuonly.'  '. + ''; my $sections=''; my %sectionhash = &Apache::loncommon::get_sections(); @@ -2489,7 +2621,7 @@ function group_or_section(caller) { } if (%grouphash) { - $groups=&mt('Group:').' '; - foreach my $item ('listing','scope','urls') { + foreach my $item (@components) { $output .= ''; } $output .= ''; foreach my $item (@components) { $output .= '
'.$titles{$item}.'
'; - if ($item eq 'urls') { + if (($item eq 'urls') || ($item eq 'menus')) { my $selected = $values{$item}; foreach my $option (@{$options{$item}}) { - if ($option eq 'lti') { + if (($item eq 'urls') && ($option eq 'lti')) { next unless (keys(%posslti)); + } elsif (($item eq 'menus') && ($option eq 'colls')) { + next unless (@possmenus); } my $checked; - if ($selected =~ /^\Q$option\E/) { + 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 ($selected =~ /^\Q$option\E/) { $checked = ' checked="checked"'; } my $onclick; @@ -4833,7 +5080,7 @@ sub string_deeplink_selector { $output .= ''; - if ($option eq 'key') { + if (($item eq 'urls') && ($option eq 'key')) { my $visibility="hidden"; my $currkey; if ($checked) { @@ -4841,26 +5088,42 @@ sub string_deeplink_selector { $currkey = (split(/\:/,$values{$item}))[1]; } $output .= ' '. - ''; - } elsif ($option eq 'lti') { + ''; + } elsif (($option eq 'lti') || ($option eq 'colls')) { my $display="none"; - my ($currlti,$blankcheck); + my ($current,$blankcheck,@possibles); if ($checked) { $display = 'inline-block'; - $currlti = (split(/\:/,$values{$item}))[1]; + if ($option eq 'lti') { + $current = (split(/\:/,$selected))[1]; + } else { + $current = $selected; + } } else { $blankcheck = ' selected="selected"'; } + if ($option eq 'lti') { + @possibles = keys(%posslti); + } else { + @possibles = @possmenus; + } $output .= '
 
'; } @@ -4924,7 +5187,7 @@ 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, and access scope']], + => [['on','Set choices for link protection, resource listing, access scope, and shown menu items']], ); @@ -4935,7 +5198,7 @@ my %stringmatches = ( => [['_allowfrom_','[^\!]+'], ['_denyfrom_','\!']], 'string_deeplink' - => [['on','^(full|absent|grades|details|datestatus)\,(res|map|rec)\,(any|only|key\:\w+|lti\:\d+)$']], + => [['on','^(full|absent|grades|details|datestatus)\,(res|map|rec)\,(any|only|key\:\w+|lti\:\d+)\,(\d+|)$']], ); my %stringtypes = ( @@ -5405,6 +5668,18 @@ sub dateshift { my $dom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $crs = $env{'course.'.$env{'request.course.id'}.'.num'}; my $sec = $env{'request.course.sec'}; + my $secgrpregex; + if ($sec ne '') { + my @groups; + if ($env{'request.course.groups'} ne '') { + @groups = split(/:/,$env{'request.course.groups'}); + } + if (@groups) { + $secgrpregex = '(?:'.join('|',($sec,@groups)).')'; + } else { + $secgrpregex = $sec; + } + } my %data=&Apache::lonnet::dump('resourcedata',$dom,$crs); # ugly retro fix for broken version of types foreach my $key (keys(%data)) { @@ -5420,7 +5695,7 @@ sub dateshift { foreach my $key (keys(%data)) { if ($data{$key.'.type'}=~/^date_(start|end)$/) { if ($sec ne '') { - next unless ($key =~ /^$env{'request.course.id'}\.\[$sec\]\./); + next unless ($key =~ /^$env{'request.course.id'}\.\[$secgrpregex\]\./); } my $newdate=$data{$key}+$shift; $$numchanges ++; @@ -5840,7 +6115,15 @@ sub date_shift_one { text=>"Shifting Dates"}); my $submit_text = &mt('Shift all dates accordingly'); if ($sec ne '') { - $submit_text = &mt("Shift all dates set explicitly for section '[_1]', accordingly",$sec); + my @groups; + if ($env{'request.course.groups'} ne '') { + @groups = split(/:/,$env{'request.course.groups'}); + } + if (@groups) { + $submit_text = &mt("Shift dates set just for your section/group(s), accordingly"); + } else { + $submit_text = &mt("Shift dates set just for your section, accordingly"); + } } my $start_page=&Apache::loncommon::start_page('Shift Dates'); my $breadcrumbs = &Apache::lonhtmlcommon::breadcrumbs('Shift'); @@ -5880,11 +6163,23 @@ sub date_shift_two { my $timeshifted=&Apache::lonhtmlcommon::get_date_from_form('timeshifted'); $r->print('

'.&mt('Shift Dates').'

'); if ($sec ne '') { - $r->print('

'. - &mt("Shift all dates set explicitly for section '[_1]', such that [_2] becomes [_3]", - $sec,&Apache::lonlocal::locallocaltime($env{'form.timebase'}), - &Apache::lonlocal::locallocaltime($timeshifted)). - '

'); + my @groups; + if ($env{'request.course.groups'} ne '') { + @groups = split(/:/,$env{'request.course.groups'}); + } + if (@groups) { + $r->print('

'. + &mt("Shift dates set just for your section/group(s), such that [_1] becomes [_2]", + &Apache::lonlocal::locallocaltime($env{'form.timebase'}), + &Apache::lonlocal::locallocaltime($timeshifted)). + '

'); + } else { + $r->print('

'. + &mt("Shift dates set just for your section, such that [_1] becomes [_2]", + &Apache::lonlocal::locallocaltime($env{'form.timebase'}), + &Apache::lonlocal::locallocaltime($timeshifted)). + '

'); + } } else { $r->print('

'.&mt('Shifting all dates such that [_1] becomes [_2]', &Apache::lonlocal::locallocaltime($env{'form.timebase'}), @@ -6856,6 +7151,12 @@ sub parm_change_log { } if ($last) { ($folder) = &Apache::lonnet::decode_symb($last); } } + my $numgroups = 0; + my @groups; + if ($env{'request.course.groups'} ne '') { + @groups = split(/:/,$env{'request.course.groups'}); + $numgroups = scalar(@groups); + } foreach my $id (sort { if ($parmlog{$b}{'exe_time'} ne $parmlog{$a}{'exe_time'}) { return $parmlog{$b}{'exe_time'} <=>$parmlog{$a}{'exe_time'} @@ -6895,7 +7196,8 @@ sub parm_change_log { my ($realm,$section,$parmname,$part,$what,$middle,$uname,$udom,$issection,$realmdescription)= &components($changed,$parmlog{$id}{'uname'},$parmlog{$id}{'udom'},$typeflag); if ($env{'request.course.sec'} ne '') { - next if (($issection ne '') && ($issection ne $env{'request.course.sec'})); + next if (($issection ne '') && (!(($issection eq $env{'request.course.sec'}) || + ($numgroups && (grep(/^\Q$issection\E$/,@groups)))))); if ($uname ne '') { my $stusection = &Apache::lonnet::getsection($uname,$udom,$env{'request.course.id'}); next if (($stusection ne '-1') && ($stusection ne $env{'request.course.sec'}));