--- loncom/interface/lonparmset.pm 2019/03/02 15:40:14 1.591 +++ loncom/interface/lonparmset.pm 2022/04/30 19:29:43 1.610 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set parameters for assessments # -# $Id: lonparmset.pm,v 1.591 2019/03/02 15:40:14 raeburn Exp $ +# $Id: lonparmset.pm,v 1.610 2022/04/30 19:29:43 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -976,10 +976,9 @@ sub storeparm_by_symb_inner { # # @param {string} $value - the parameter value # @param {string} $type - the parameter type -# @param {string} $name - the parameter name (unused) # @param {boolean} $editable - Set to true to get an icon when no value is defined. sub valout { - my ($value,$type,$name,$editable)=@_; + my ($value,$type,$editable)=@_; my $result = ''; # Values of zero are valid. if (! $value && $value ne '0') { @@ -1071,7 +1070,7 @@ sub plink { my ($type,$dis,$value,$marker,$return,$call,$recursive,$extra)=@_; my $winvalue=$value; unless ($winvalue) { - if ((&isdateparm($type) || (&is_specialstring($type))) { + if (&isdateparm($type) || (&is_specialstring($type))) { $winvalue=$env{'form.recent_'.$type}; } elsif ($type eq 'string_yesno') { if ($env{'form.recent_string'} =~ /^(yes|no)$/i) { @@ -1084,7 +1083,7 @@ sub plink { my ($parmname)=((split(/\&/,$marker))[1]=~/\_([^\_]+)$/); my ($hour,$min,$sec,$val)=&preset_defaults($parmname); unless (defined($winvalue)) { $winvalue=$val; } - my $valout = &valout($value,$type,$parmname,1); + my $valout = &valout($value,$type,1); my $unencmarker = $marker; foreach my $item (\$type, \$dis, \$winvalue, \$marker, \$return, \$call, \$hour, \$min, \$sec, \$extra) { @@ -1242,12 +1241,17 @@ 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_(state|others|listing|scope)_/; + var dlLinkProtectRegExp = /^deeplink_protect_/; + var dlLtidRegExp = /^deeplink_ltid_/; + var dlLticRegExp = /^deeplink_ltic_/; + 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) { @@ -1276,61 +1280,133 @@ 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 (dlLinkProtectRegExp.test(name)) { + if (document.parmform.elements[i].checked) { + var identifier = name.replace(dlLinkProtectRegExp,''); + 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 (dlLtidRegExp.test(name)) { + var identifier = name.replace(dlLtidRegExp,''); + if (isRadioSet('deeplink_protect_'+identifier,'ltid')) { + var possltid = document.parmform.elements[i].value; + possltid = possltid.replace(/\D+/g,''); + if (possltid.length) { + if (document.parmform.elements['set_'+identifier].value) { + possltid = ':'+possltid; + } + 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."); + return false; + } + } + } else if (dlLticRegExp.test(name)) { + var identifier = name.replace(dlLticRegExp,''); + if (isRadioSet('deeplink_protect_'+identifier,'ltic')) { + var possltic = document.parmform.elements[i].value; + possltic = possltic.replace(/\D+/g,''); + if (possltic.length) { + if (document.parmform.elements['set_'+identifier].value) { + possltic = ':'+possltic; + } + 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."); + return false; + } + } + } else if (dlKeyRegExp.test(name)) { + var identifier = name.replace(dlKeyRegExp,''); + if (isRadioSet('deeplink_protect_'+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; } } } @@ -1338,6 +1414,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(%lti))) { + if (($item =~ /^\d+$/) && (ref($lti{$item}) eq 'HASH')) { + $domltistr .= $item.':'.&escape(&escape($lti{$item}{'name'})).','; + } + } + $domltistr =~ s/,$//; + if ($domltistr) { + $extra = 'ltid_'.$domltistr; + } + } + my %courselti = &Apache::lonnet::get_course_lti($cnum,$cdom); + if (keys(%courselti)) { + foreach my $item (sort { $a <=> $b } keys(%courselti)) { + if (($item =~ /^\d+$/) && (ref($courselti{$item}) eq 'HASH')) { + $crsltistr .= $item.':'.&escape(&escape($courselti{$item}{'name'})).','; + } + } + $crsltistr =~ s/,$//; + if ($crsltistr) { + if ($extra) { + $extra .= '&'; } + $extra .= 'ltic_'.$crsltistr; } } - if (keys(%posslti)) { - $extra = 'lti_'; - foreach my $lti (sort { $a <=> $b } keys(%posslti)) { - $extra .= $lti.':'.&js_escape($posslti{$lti}).','; + 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); } - $extra =~ s/,$//; } } if ($parmlev eq 'general') { @@ -1716,13 +1860,13 @@ sub print_row { } } my ($parmname)=($thismarker=~/\_([^\_]+)$/); - $effective_parm = &valout($recursinfo->[0],$recursinfo->[1],$parmname); + $effective_parm = &valout($recursinfo->[0],$recursinfo->[1]); $r->print(''.$effective_parm. '
'.$rectitle.' '. $effparm_level.''); } else { if ($result) { - $effective_parm = &valout($outpar[$result],$typeoutpar[$result],$parmname); + $effective_parm = &valout($outpar[$result],$typeoutpar[$result]); } if ($eff_groupparm) { $effective_parm = $eff_groupparm; @@ -1739,7 +1883,7 @@ sub print_row { $sessionvaltype=$$defaulttype{$which}; } $r->print(''. - &valout($sessionval,$sessionvaltype,$$name{$which}).' '. + &valout($sessionval,$sessionvaltype).' '. ''); } $r->print(''); @@ -1767,7 +1911,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)=@_; @@ -1809,9 +1953,7 @@ sub print_td { $nolink = 1; } } elsif ($mprefix =~ /availablestudent\&$/) { - if ($which > 4) { - $nolink = 1; - } + $nolink = 1; } elsif ($mprefix =~ /examcode\&$/) { unless ($which == 2) { $nolink = 1; @@ -1820,7 +1962,7 @@ sub print_td { } if ($nolink) { my ($parmname)=((split(/\&/,$mprefix))[1]=~/\_([^\_]+)$/); - $r->print(&valout($currval,$currtype,$parmname)); + $r->print(&valout($currval,$currtype)); } else { $r->print(&plink($currtype, $$display{$value},$currval, @@ -1860,7 +2002,7 @@ sub check_other_groups { if ($result > 3) { $bgcolor = '#AAFFAA'; } - $grp_parm = &valout($coursereply,$resulttype,$parmname); + $grp_parm = &valout($coursereply,$resulttype); $output = ''; if ($resultgroup && $resultlevel) { if ($resultlevel eq 'recursive') { @@ -1932,6 +2074,7 @@ sub parm_control_group { # @param {hash reference} $uris - hash resource/map id -> resource src # @param {hash reference} $keyorder - hash parameter key -> appearance rank for this parameter when looking through every resource and every parameter, starting at 100 (integer) # @param {hash reference} $defkeytype - hash parameter name -> parameter type +# @param {string} $pssymb - resource symb (when a single resource is selected) sub extractResourceInformation { my $ids = shift; my $typep = shift; @@ -1945,11 +2088,22 @@ sub extractResourceInformation { my $uris=shift; my $keyorder=shift; my $defkeytype=shift; + my $pssymb=shift; my $keyordercnt=100; my $navmap = Apache::lonnavmaps::navmap->new(); - my @allres=$navmap->retrieveResources(undef,undef,1,undef,1); + return unless(ref($navmap)); + my @allres; + if ($pssymb ne '') { + my $res = $navmap->getBySymb($pssymb); + if (ref($res)) { + @allres = ($res); + } + } + if (!@allres) { + @allres=$navmap->retrieveResources(undef,undef,1,undef,1); + } foreach my $resource (@allres) { my $id=$resource->id(); my ($mapid,$resid)=split(/\./,$id); @@ -2073,7 +2227,7 @@ sub isdateparm { sub is_specialstring { my $type=shift; - return (($type=~/^string_/) && (($type ne 'string_yesno'))); + return (($type=~/^string_/) && ($type ne 'string_yesno')); } # Prints the HTML and Javascript to select parameters, with various shortcuts. @@ -2421,9 +2575,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(); @@ -2490,7 +2672,7 @@ function group_or_section(caller) { } if (%grouphash) { - $groups=&mt('Group:').' print(' checked="checked"'); + my ($r,$sortorder,$context)=@_; + my %text; + if ($context eq 'newoverview') { + %text = &Apache::lonlocal::texthash ( + realmstudent => 'Sort by location in course first, then student (group/section)', + studentrealm => 'Sort by student (group/section) first, then location in course', + ); + } else { + %text = &Apache::lonlocal::texthash ( + realmstudent => 'Sort by realm first, then student (group/section)', + studentrealm => 'Sort by student (group/section) first, then realm', + ); } - $r->print(' />'.&mt('Sort by realm first, then student (group/section)')); - $r->print('
'); } # Returns a hash parameter key -> order (integer) giving the order for some parameters. @@ -3065,27 +3266,124 @@ sub assessparms { $csec=&Apache::lonnet::getsection($udom,$uname, $env{'request.course.id'}); if ($csec eq '-1') { - $message= - '

'. - &mt('User [_1] at domain [_2] not in this course', - "'".$uname."'","'".$udom."'"). - '

'; - $uname=''; - $csec=$env{'form.csec'}; + my $crstype = $env{'course.'.$env{'request.course.id'}.'.type'}; + if ($env{'form.userroles'} eq 'any') { + if (($env{'user.name'} eq $uname) && ($env{'user.domain'} eq $udom)) { + $csec = $env{'request.course.sec'}; + $message = ''; + if ($crstype eq 'Community') { + $message .= &mt('User [_1] at domain [_2] has a non-member role in this community', + $uname,$udom); + } else { + $message .= &mt('User [_1] at domain [_2] has a non-student role in this course', + $uname,$udom); + } + $message .= ''; + } else { + my @possroles = ('in','ep','ta','cr'); + if ($crstype eq 'Community') { + unshift(@possroles,'co'); + } else { + unshift(@possroles,'cc'); + } + my %not_student_roles = + &Apache::lonnet::get_my_roles($uname,$udom,'userroles',['active'], + \@possroles,[$udom],1,1); + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my %sections_by_role; + foreach my $role (keys(%not_student_roles)) { + if ($role =~ /^\Q$cnum:$cdom:\E([^:]+):(|[^:]+)$/) { + my ($rolename,$sec) = ($1,$2); + if ($rolename =~ m{^cr/}) { + $rolename = 'cr'; + } + push(@{$sections_by_role{$rolename}},$sec); + } + } + my $numroles = scalar(keys(%sections_by_role)); + if ($numroles) { + foreach my $role (@possroles) { + if (ref($sections_by_role{$role}) eq 'ARRAY') { + my @secs = sort { $a <=> $b } @{$sections_by_role{$role}}; + $csec = $secs[0]; + last; + } + } + } + if ($csec eq '-1') { + $message = ''; + if ($crstype eq 'Community') { + $message .= &mt('User [_1] at domain [_2] does not have a role in this community', + $uname,$udom); + } else { + $message .= &mt('User [_1] at domain [_2] does not have a role in this course', + $uname,$udom); + } + $message .= ''; + $uname=''; + if ($env{'request.course.sec'} ne '') { + $csec=$env{'request.course.sec'}; + } else { + $csec=$env{'form.csec'}; + } + $cgroup=$env{'form.cgroup'}; + } else { + $message = ''; + if ($crstype eq 'Community') { + $message .= &mt('User [_1] at domain [_2] has a non-member role in this community', + $uname,$udom); + } else { + $message .= &mt('User [_1] at domain [_2] has a non-student role in this course', + $uname,$udom); + } + $message .= ''; + } + } + } else { + $message = ''; + if ($crstype eq 'Community') { + $message .= &mt('User [_1] at domain [_2] does not have a member role in this community', + $uname,$udom); + } else { + $message .= &mt('User [_1] at domain [_2] does not have a student role in this course', + $uname,$udom); + } + $message .= ''; + $uname=''; + if ($env{'request.course.sec'} ne '') { + $csec=$env{'request.course.sec'}; + } else { + $csec=$env{'form.csec'}; + } + $cgroup=$env{'form.cgroup'}; + } + } elsif ($env{'request.course.sec'} ne '') { + if ($csec ne $env{'request.course.sec'}) { + $message=''. + &mt("User '[_1]' at domain '[_2]' not in section '[_3]'", + $uname,$udom,$env{'request.course.sec'}). + ''; + $uname=''; + $csec=$env{'request.course.sec'}; + } $cgroup=$env{'form.cgroup'}; - } else { + } + if ($uname ne '') { my %name=&Apache::lonnet::userenvironment($udom,$uname, ('firstname','middlename','lastname','generation','id')); - $message="\n

\n".&mt("Full Name").": ". - $name{'firstname'}.' '.$name{'middlename'}.' ' - .$name{'lastname'}.' '.$name{'generation'}. - "
\n".&mt('Student/Employee ID').": ".$name{'id'}.'

'; - } - @usersgroups = &Apache::lonnet::get_users_groups( - $udom,$uname,$env{'request.course.id'}); - if (@usersgroups > 0) { - unless (grep(/^\Q$cgroup\E$/,@usersgroups)) { - $cgroup = $usersgroups[0]; + $message .= "\n

\n".&mt('Full Name').': ' + .$name{'firstname'}.' '.$name{'middlename'}.' ' + .$name{'lastname'}.' '.$name{'generation'} + ."
\n".&mt('Student/Employee ID').': '.$name{'id'}.'

'; + @usersgroups = &Apache::lonnet::get_users_groups( + $udom,$uname,$env{'request.course.id'}); + if (@usersgroups > 0) { + unless (grep(/^\Q$cgroup\E$/,@usersgroups)) { + $cgroup = $usersgroups[0]; + } + } else { + $cgroup = ''; } } } @@ -3097,7 +3395,7 @@ sub assessparms { # --------------------------------------------------------- Get all assessments &extractResourceInformation(\@ids, \%typep,\%keyp, \%allparms, \%allparts, \%allmaps, \%mapp, \%symbp,\%maptitles,\%uris, - \%keyorder); + \%keyorder,undef,$pssymb); %allmaps_inverted = reverse(%allmaps); @@ -3114,6 +3412,7 @@ sub assessparms { my $chome = $env{'course.'.$env{'request.course.id'}.'.home'}; my ($got_chostname,$chostname,$cmajor,$cminor); my $totalstored = 0; + my $totalskippeduser = 0; my $now = time; for (my $i=0;$i<=$#markers;$i++) { my ($needsrelease,$needsnewer,$name,$namematch); @@ -3122,6 +3421,11 @@ sub assessparms { } if ($markers[$i] =~ /\&(8|7|6|5)$/) { next if ($noeditgrp); + } elsif ($markers[$i] =~ /\&(4|3|2|1)$/) { + if ($uname eq '') { + $totalskippeduser ++; + next; + } } if ($markers[$i] =~ /\&(17|11|7|3)$/) { $namematch = 'maplevelrecurse'; @@ -3260,9 +3564,27 @@ sub assessparms { # ---------------------------------------------------------------- Done storing if ($totalstored) { $message.='

' + .&mt('Changes for [quant,_1,parameter] saved.',$totalstored) + .'
' .&mt('Changes can take up to 10 minutes before being active for all students.') .&Apache::loncommon::help_open_topic('Caching') .'

'; + } else { + $message.='

'.&mt('No parameter changes saved.').'

'; + } + if ($totalskippeduser) { + $message .= '

'; + if ($uhome eq 'no_host') { + $message .= &mt('Changes for [quant,_1,user-specific parameter] not saved because the username or ID was invalid.', + $totalskippeduser); + } elsif ($env{'form.userroles'} eq 'any') { + $message .= &mt('Changes for [quant,_1,user-specific parameter] not saved because the user does not have a course role.', + $totalskippeduser); + } else { + $message .= &mt('Changes for [quant,_1,user-specific parameter] not saved because the user is not a student.', + $totalskippeduser); + } + $message .= '

'; } } @@ -3293,13 +3615,20 @@ sub assessparms { # ----- Start Parameter Selection - # Hide parm selection? + # Hide parm selection and possibly table? + my ($tablejs,$tabledivsty); + if (((($env{'form.uname'} ne '') || ($env{'form.id'} ne '')) && ($uname eq '')) && + ($env{'form.dis'}) && ($pssymb eq '')) { + $tablejs = 'document.getElementById('."'parmtable'".').style.display = "";'; + $tabledivsty = ' style="display:none"'; + } $r->print(< // @@ -3326,7 +3655,7 @@ ENDPARMSELSCRIPT $r->print(&Apache::lonhtmlcommon::start_pick_box(undef,'parmlevel')); &levelmenu($r,\%alllevs,$parmlev); $r->print(&Apache::lonhtmlcommon::row_closure()); - &mapmenu($r,\%allmaps,$pschp,\%maptitles, \%symbp); + &mapmenu($r,\%allmaps,$pschp,\%maptitles,\%symbp,$parmlev); $r->print(&Apache::lonhtmlcommon::row_closure()); $r->print(&Apache::lonhtmlcommon::row_title(&mt('Select Parts to View'))); &partmenu($r,\%allparts,\@psprt); @@ -3406,6 +3735,7 @@ ENDPARMSELSCRIPT if ($parm_permission->{'edit'}) { undef($readonly); } + $r->print('
'); if ($parmlev eq 'full') { # @@ -3622,7 +3952,7 @@ ENDTABLEHEADFOUR #-------------------------------------------- for each map, gather information my $mapid; - foreach $mapid (sort {$maplist{$a} cmp $maplist{$b}} keys(%maplist)) { + foreach $mapid (sort { $a <=> $b } keys(%maplist)) { my $maptitle = $maplist{$mapid}; #----------------------- loop through ids and get all parameter types for map @@ -3839,6 +4169,7 @@ ENDMAPONE .'' ); } # end of $parmlev eq general + $r->print('
'); } $r->print(''); if ($numreclinks) { @@ -4376,9 +4707,12 @@ sub parse_listdata_key { # @param {string} $caller - name of the calling sub (overview|newoverview) # @param {hash reference} $classlist - from loncoursedata::get_classlist # @param {boolean} $readonly - true if editing not allowed +# @param {string} $parmlev - full|map +# @param {hash reference} $hash_for_realm - keys: realm, values: numeric order +# @param {string} $pschp - selected map pc, or 'all' # @returns{integer} - number of $listdata parameters processed sub listdata { - my ($r,$resourcedata,$listdata,$sortorder,$caller,$classlist,$readonly)=@_; + my ($r,$resourcedata,$listdata,$sortorder,$caller,$classlist,$readonly,$parmlev,$hash_for_realm,$pschp)=@_; # Start list output @@ -4389,6 +4723,7 @@ sub listdata { $tableopen=0; my $foundkeys=0; my %keyorder=&standardkeyorder(); + my $readonlyall = $readonly; my ($secidx,%grouphash); if (($env{'request.course.sec'} ne '') && ($caller eq 'overview')) { @@ -4403,6 +4738,20 @@ sub listdata { foreach my $key (sort { my ($astudent,$ares,$apart,$aparm) = &parse_listdata_key($a,$listdata); my ($bstudent,$bres,$bpart,$bparm) = &parse_listdata_key($b,$listdata); + my ($aid,$bid); + if ($caller eq 'newoverview') { + if (ref($hash_for_realm) eq 'HASH') { + if (($parmlev eq 'map') && ($pschp eq 'all')) { + my ($aurl) = ($ares =~ /^(.+\.(?:sequence|page))___\(all\)$/); + my ($burl) = ($bres =~ /^(.+\.(?:sequence|page))___\(all\)$/); + $aid = $hash_for_realm->{$aurl}; + $bid = $hash_for_realm->{$burl}; + } elsif ($parmlev eq 'full') { + $aid = $hash_for_realm->{$ares}; + $bid = $hash_for_realm->{$bres}; + } + } + } # get the numerical order for the param $aparm=$keyorder{'parameter_0_'.$aparm}; @@ -4412,7 +4761,21 @@ sub listdata { if ($sortorder eq 'realmstudent') { if ($ares ne $bres ) { - $result = ($ares cmp $bres); + if ($caller eq 'newoverview') { + if (ref($hash_for_realm) eq 'HASH') { + if (($parmlev eq 'map') && ($pschp eq 'all')) { + $result = ($aid <=> $bid); + } elsif ($parmlev eq 'full') { + $result = ($aid <=> $bid); + } else { + $result = ($ares cmp $bres); + } + } else { + $result = ($ares cmp $bres); + } + } else { + $result = ($ares cmp $bres); + } } elsif ($astudent ne $bstudent) { $result = ($astudent cmp $bstudent); } elsif ($apart ne $bpart ) { @@ -4422,7 +4785,21 @@ sub listdata { if ($astudent ne $bstudent) { $result = ($astudent cmp $bstudent); } elsif ($ares ne $bres ) { - $result = ($ares cmp $bres); + if ($caller eq 'newoverview') { + if (ref($hash_for_realm) eq 'HASH') { + if (($parmlev eq 'map') && ($pschp eq 'all')) { + $result = ($aid <=> $bid); + } elsif ($parmlev eq 'full') { + $result = ($aid <=> $bid); + } else { + $result = ($ares cmp $bres); + } + } else { + $result = ($ares cmp $bres); + } + } else { + $result = ($ares cmp $bres); + } } elsif ($apart ne $bpart ) { $result = ($apart cmp $bpart); } @@ -4450,6 +4827,8 @@ sub listdata { my ($middle,$part,$name)= ($thiskey=~/^$env{'request.course.id'}\.(?:(.+)\.)*([\w\s\-]+)\.(\w+)$/); my $section=&mt('All Students'); + $readonly = $readonlyall; + my $userscope; my $showval = $$resourcedata{$thiskey}; if ($middle=~/^\[(.*)\]/) { my $issection=$1; @@ -4463,6 +4842,7 @@ sub listdata { } } $section=&mt('User').": ".&Apache::loncommon::plainname($stuname,$studom); + $userscope = 1; } else { if (($env{'request.course.sec'} ne '') && ($caller eq 'overview')) { if (exists($grouphash{$issection})) { @@ -4499,6 +4879,12 @@ sub listdata { } if ($is_map) { my $leveltitle = &mt('Folder/Map'); + my $title = &Apache::lonnet::gettitle($1); + if (ref($hash_for_realm) eq 'HASH') { + if ($hash_for_realm->{$mapurl} eq '1') { + $title = &mt('Main Content'); + } + } unless (($name eq 'hiddenresource') || ($name eq 'encrypturl')) { if ($caller eq 'newoverview') { my $altkey = $thiskey; @@ -4515,9 +4901,10 @@ sub listdata { $is_recursive = 1; } } - $realm=''.$leveltitle.': '.&Apache::lonnet::gettitle($mapurl).'
('.$mapurl.')
'; + $realm=''.$leveltitle.': '.$title.'
('.$mapurl.')
'; } elsif ($middle) { my ($map,$id,$url)=&Apache::lonnet::decode_symb($middle); + next if (($url =~ /\.(page|sequence)$/) && ($parmlev eq 'full') && ($caller eq 'newoverview')); $realm=''.&mt('Resource'). ': '.&Apache::lonnet::gettitle($middle). '
('.$url.' in '.$map.' id: '. @@ -4560,8 +4947,13 @@ sub listdata { ''.&mt($parmitem). ''); unless ($readonly) { + my $disabled; + if (($name eq 'availablestudent') && + (($showval eq '') || ($userscope))) { + $disabled = ' disabled="disabled"'; + } $r->print(''); + $thiskey.'"'.$disabled.' />'); } $r->print(''); $foundkeys++; @@ -4589,6 +4981,9 @@ sub listdata { $r->print(&date_interval_selector($thiskey,$name, $showval,$readonly)); } elsif ($thistype =~ m/^string/) { + if ($name eq 'availablestudent') { + $readonly = 1; + } $r->print(&string_selector($thistype,$thiskey, $showval,$name,$readonly)); } else { @@ -4743,19 +5138,31 @@ sub string_ip_selector { sub string_deeplink_selector { my ($thiskey, $showval, $readonly) = @_; - my (@components,%values,@current,%titles,%options,%optiontext,%defaults,%posslti); - @components = ('listing','scope','urls'); + my (@components,%values,@current,%titles,%options,%optiontext,%defaults, + %selectnull,%domlti,%crslti,@possmenus); + @components = ('state','others','listing','scope','protect','menus'); %titles = &Apache::lonlocal::texthash ( + state => 'Access status', + others => 'Hide other resources', listing => 'In Contents and/or Gradebook', scope => 'Access scope for link', - urls => 'Supported link types', + protect => 'Link protection', + menus => 'Menu Items Displayed', ); %options = ( + state => ['only','off','both'], + others => ['hide','unhide'], listing => ['full','absent','grades','details','datestatus'], scope => ['res','map','rec'], - urls => ['any','only','key','lti'], + protect => ['none','key','ltid','ltic'], + menus => ['std','colls'], ); %optiontext = &Apache::lonlocal::texthash ( + only => 'deep only', + off => 'deeplink off', + both => 'regular + deep', + hide => 'Hidden', + unhide => 'Unhidden', full => 'Listed (linked) in both', absent => 'Not listed', grades => 'Listed in grades only', @@ -4764,50 +5171,94 @@ sub string_deeplink_selector { res => 'resource only', map => 'enclosing map/folder', rec => 'recursive map/folder', - any => 'regular + deep', - only => 'deep only', - key => 'deep with key', - lti => 'deep with LTI launch', + none => 'not in use', + key => 'key access', + ltic => 'LTI access (course)', + ltid => 'LTI access (domain)' , + std => 'Standard (all menus)', + colls => 'Numbered collection', + ); + %selectnull = &Apache::lonlocal::texthash ( + ltic => 'Select Launcher', + ltid => 'Select Launcher', + colls => 'Select', ); if ($showval =~ /,/) { + %values=(); @current = split(/,/,$showval); - ($values{'listing'}) = ($current[0] =~ /^(full|absent|grades|details|datestatus)$/); - ($values{'scope'}) = ($current[1] =~ /^(res|map|rec)$/); - ($values{'urls'}) = ($current[2] =~ /^(any|only|key:\w+|lti:\d+)$/); + ($values{'state'}) = ($current[0] =~ /^(only|off|both)$/); + ($values{'others'}) = ($current[1] =~ /^(hide|unhide)$/); + ($values{'listing'}) = ($current[2] =~ /^(full|absent|grades|details|datestatus)$/); + ($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+)$/); } else { + $defaults{'state'} = 'off', + $defaults{'others'} = 'unhide', $defaults{'listing'} = 'full'; $defaults{'scope'} = 'res'; - $defaults{'urls'} = 'any'; + $defaults{'protect'} = 'none'; + $defaults{'menus'} = '0'; } my $disabled; if ($readonly) { $disabled=' disabled="disabled"'; } - my %lti = + my %courselti = + &Apache::lonnet::get_course_lti($env{'course.'.$env{'request.course.id'}.'.num'}, + $env{'course.'.$env{'request.course.id'}.'.domain'}); + foreach my $item (keys(%courselti)) { + if (ref($courselti{$item}) eq 'HASH') { + $crslti{$item} = $courselti{$item}{'name'}; + } + } + my %lti = &Apache::lonnet::get_domain_lti($env{'course.'.$env{'request.course.id'}.'.domain'}, - 'provider'); + 'linkprot'); foreach my $item (keys(%lti)) { - if (ref($lti{$item}) eq 'HASH') { - unless ($lti{$item}{'requser'}) { - $posslti{$item} = $lti{$item}{'consumer'}; + if (($item =~ /^\d+$/) && (ref($lti{$item}) eq 'HASH')) { + $domlti{$item} = $lti{$item}{'name'}; + } + } + if ($env{'course.'.$env{'request.course.id'}.'.menucollections'}) { + foreach my $item (split(/;/,$env{'course.'.$env{'request.course.id'}.'.menucollections'})) { + my ($num,$value) = split(/\%/,$item); + if ($num =~ /^\d+$/) { + push(@possmenus,$num); } } } + my $output = ''; - 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 'protect') || ($item eq 'menus')) { my $selected = $values{$item}; foreach my $option (@{$options{$item}}) { - if ($option eq 'lti') { - next unless (keys(%posslti)); + 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 ($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; @@ -4818,7 +5269,7 @@ sub string_deeplink_selector { $output .= ''; - if ($option eq 'key') { + if (($item eq 'protect') && ($option eq 'key')) { my $visibility="hidden"; my $currkey; if ($checked) { @@ -4826,26 +5277,46 @@ sub string_deeplink_selector { $currkey = (split(/\:/,$values{$item}))[1]; } $output .= ' '. - ''; - } elsif ($option eq 'lti') { + ''; + } elsif (($option eq 'ltic') || ($option eq 'ltid') || ($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 'ltic') || ($option eq 'ltid')) { + $current = (split(/\:/,$selected))[1]; + } 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 .= '
 
'; } @@ -4909,7 +5380,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']], ); @@ -4920,7 +5391,7 @@ my %stringmatches = ( => [['_allowfrom_','[^\!]+'], ['_denyfrom_','\!']], 'string_deeplink' - => [['on','^(full|absent|grades|details|datestatus)\,(res|map|rec)\,(any|only|key\:\w+|lti\:\d+)$']], + => [['on','^(only|off|both)\,(hide|unhide)\,(full|absent|grades|details|datestatus)\,(res|map|rec)\,(none|key\:\w+|ltic\:\d+|ltid\:\d+)\,(\d+|)$']], ); my %stringtypes = ( @@ -5386,9 +5857,22 @@ sub oldversion_warning { # @param {integer} $shift - time to shift, in seconds # @returns {string} - error name or 'ok' sub dateshift { - my ($shift)=@_; + my ($shift,$numchanges)=@_; 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)) { @@ -5403,7 +5887,11 @@ sub dateshift { # go through all parameters and look for dates foreach my $key (keys(%data)) { if ($data{$key.'.type'}=~/^date_(start|end)$/) { + if ($sec ne '') { + next unless ($key =~ /^$env{'request.course.id'}\.\[$secgrpregex\]\./); + } my $newdate=$data{$key}+$shift; + $$numchanges ++; $storecontent{$key}=$newdate; } } @@ -5510,7 +5998,7 @@ ENDOVER &extractResourceInformation(\@ids, \%typep,\%keyp, \%allparms, \%allparts, \%allmaps, \%mapp, \%symbp,\%maptitles,\%uris, - \%keyorder,\%defkeytype); + \%keyorder,\%defkeytype,$pssymb); if (grep {$_ eq 'all'} (@psprt)) { @psprt = keys(%allparts); @@ -5522,10 +6010,8 @@ ENDOVER $r->print('
'); $r->print(&Apache::lonhtmlcommon::start_pick_box(undef,'parmlevel')); &levelmenu($r,\%alllevs,$parmlev); - if ($parmlev ne 'general') { - $r->print(&Apache::lonhtmlcommon::row_closure()); - &mapmenu($r,\%allmaps,$pschp,\%maptitles,\%symbp); - } + $r->print(&Apache::lonhtmlcommon::row_closure()); + &mapmenu($r,\%allmaps,$pschp,\%maptitles,\%symbp,$parmlev); $r->print(&Apache::lonhtmlcommon::row_closure(1)); $r->print(&Apache::lonhtmlcommon::end_pick_box()); $r->print('
'); @@ -5563,7 +6049,7 @@ ENDOVER $r->print('
'); my $sortorder=$env{'form.sortorder'}; unless ($sortorder) { $sortorder='realmstudent'; } - &sortmenu($r,$sortorder); + &sortmenu($r,$sortorder,'newoverview')); $r->print('
'); $r->print('

'); @@ -5588,7 +6074,15 @@ ENDOVER # List data - &listdata($r,$resourcedata,$listdata,$sortorder,'newoverview',undef,$readonly); + my $hash_for_realm; + if (($parmlev eq 'map') && (keys(%allmaps))) { + %{$hash_for_realm} = reverse(%allmaps); + } elsif (($parmlev eq 'full') && (keys(%symbp))) { + for (my $i=0; $i<@ids; $i++) { + $hash_for_realm->{$symbp{$ids[$i]}} = $i; + } + } + &listdata($r,$resourcedata,$listdata,$sortorder,'newoverview',undef,$readonly,$parmlev,$hash_for_realm,$pschp); } $r->print(&tableend()); unless ($readonly) { @@ -5695,7 +6189,7 @@ sub overview { my $sortorder=$env{'form.sortorder'}; unless ($sortorder) { $sortorder='realmstudent'; } - &sortmenu($r,$sortorder); + &sortmenu($r,$sortorder,'overview'); my $submitbutton = ''; @@ -5815,9 +6309,21 @@ sub date_shift_one { my $dom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $crs = $env{'course.'.$env{'request.course.id'}.'.num'}; my $crstype = $env{'course.'.$env{'request.course.id'}.'.type'}; - + my $sec = $env{'request.course.sec'}; &Apache::lonhtmlcommon::add_breadcrumb({href=>'/adm/parmset?action=dateshift1&timebase='.$env{'form.timebase'}, text=>"Shifting Dates"}); + my $submit_text = &mt('Shift all dates accordingly'); + if ($sec ne '') { + 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'); $r->print($start_page.$breadcrumbs); @@ -5833,7 +6339,7 @@ sub date_shift_one { '
'. ''. ''. - ''); + ''); &endSettingsScreen($r); $r->print(&Apache::loncommon::end_page()); } @@ -5845,6 +6351,7 @@ sub date_shift_two { my ($r) = @_; 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 $crstype = $env{'course.'.$env{'request.course.id'}.'.type'}; &Apache::lonhtmlcommon::add_breadcrumb({href=>'/adm/parmset?action=dateshift1&timebase='.$env{'form.timebase'}, text=>"Shifting Dates"}); @@ -5853,14 +6360,47 @@ sub date_shift_two { $r->print($start_page.$breadcrumbs); &startSettingsScreen($r,'parmset',$crstype); my $timeshifted=&Apache::lonhtmlcommon::get_date_from_form('timeshifted'); - $r->print('

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

'. - '

'.&mt('Shifting all dates such that [_1] becomes [_2]', - &Apache::lonlocal::locallocaltime($env{'form.timebase'}), - &Apache::lonlocal::locallocaltime($timeshifted)).'

'); + $r->print('

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

'); + if ($sec ne '') { + 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'}), + &Apache::lonlocal::locallocaltime($timeshifted)). + '

'); + } my $delta=$timeshifted-$env{'form.timebase'}; - &dateshift($delta); + my $numchanges = 0; + my $result = &dateshift($delta,\$numchanges); + if ($result eq 'ok') { + $r->print( + &Apache::lonhtmlcommon::confirm_success(&mt('Completed shifting of [quant,_1,date setting]', + $numchanges))); + } elsif ($result eq 'con_delayed') { + $r->print( + &Apache::lonhtmlcommon::confirm_success(&mt('Queued shifting of [quant,_1,date setting]', + $numchanges))); + } else { + $r->print( + &Apache::lonhtmlcommon::confirm_success(&mt('An error occurred attempting to shift dates'),1)); + } $r->print( - &Apache::lonhtmlcommon::confirm_success(&mt('Done')). '

'. &Apache::lonhtmlcommon::actionbox( [''.&mt('Content and Problem Settings').''])); @@ -6810,6 +7350,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'} @@ -6849,7 +7395,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'}));