--- loncom/interface/loncommon.pm 2022/10/18 23:28:00 1.1390 +++ loncom/interface/loncommon.pm 2023/11/17 17:02:20 1.1418 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1390 2022/10/18 23:28:00 raeburn Exp $ +# $Id: loncommon.pm,v 1.1418 2023/11/17 17:02:20 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -71,7 +71,9 @@ use Apache::lonuserutils(); use Apache::lonuserstate(); use Apache::courseclassifier(); use LONCAPA qw(:DEFAULT :match); +use LONCAPA::ltiutils; use LONCAPA::LWPReq; +use LONCAPA::map(); use HTTP::Request; use DateTime::TimeZone; use DateTime::Locale; @@ -435,7 +437,7 @@ sub studentbrowser_javascript { <script type="text/javascript" language="Javascript"> // <![CDATA[ var stdeditbrowser; - function openstdbrowser(formname,uname,udom,clicker,roleflag,ignorefilter,courseadv) { + function openstdbrowser(formname,uname,udom,clicker,roleflag,ignorefilter,courseadv,uident) { var url = '/adm/pickstudent?'; var filter; if (!ignorefilter) { @@ -456,6 +458,7 @@ sub studentbrowser_javascript { } } if ((courseadv == 'only') || (courseadv == 'none')) { url+="&courseadv="+courseadv; } + if (uident !== '') { url+="&identelement="+uident; } var title = 'Student_Browser'; var options = 'scrollbars=1,resizable=1,menubar=0'; options += ',width=700,height=600'; @@ -487,7 +490,7 @@ ENDRESBRW } sub selectstudent_link { - my ($form,$unameele,$udomele,$courseadv,$clickerid)=@_; + my ($form,$unameele,$udomele,$courseadv,$clickerid,$identelem)=@_; my $callargs = "'".&Apache::lonhtmlcommon::entity_encode($form)."','". &Apache::lonhtmlcommon::entity_encode($unameele)."','". &Apache::lonhtmlcommon::entity_encode($udomele)."'"; @@ -504,6 +507,11 @@ sub selectstudent_link { $callargs .= ",'','','$courseadv'"; } elsif ($courseadv eq 'condition') { $callargs .= ",'','','$courseadv'"; + } elsif ($identelem ne '') { + $callargs .= ",'','',''"; + } + if ($identelem ne '') { + $callargs .= ",'".&Apache::lonhtmlcommon::entity_encode($identelem)."'"; } return '<span class="LC_nobreak">'. '<a href="javascript:openstdbrowser('.$callargs.');">'. @@ -1232,7 +1240,12 @@ END $result.=">".&mt($hashref->{$value}->{'text'})."</option>\n"; } $result .= "</select>\n"; - my %select2 = %{$hashref->{$firstdefault}->{'select2'}}; + my %select2; + if (ref($hashref->{$firstdefault}) eq 'HASH') { + if (ref($hashref->{$firstdefault}->{'select2'}) eq 'HASH') { + %select2 = %{$hashref->{$firstdefault}->{'select2'}}; + } + } $result .= $middletext; $result .= "<select size=\"1\" name=\"$secondselectname\""; if ($onchangesecond) { @@ -1815,8 +1828,11 @@ sub colorfuleditor_js { save => 'Save page to make this permanent', ); &js_escape(\%js_lt); + my $showfile_js = &show_crsfiles_js(); $browse_or_search = <<"END"; + $showfile_js + function toggleChooser(form,element,titleid,only,search) { var disp = 'none'; if (document.getElementById('chooser_'+element)) { @@ -1831,22 +1847,64 @@ sub colorfuleditor_js { toggleResImport(form,element); } document.getElementById('chooser_'+element).style.display = disp; + var dirsel = ''; + var filesel = ''; + if (document.getElementById('chooser_'+element+'_crsres')) { + var currcrsres = document.getElementById('chooser_'+element+'_crsres').style.display; + if (currcrsres == 'none') { + dirsel = 'coursepath_'+element; + var filesel = 'coursefile_'+element; + var include; + if (document.getElementById('crsres_include_'+element)) { + include = document.getElementById('crsres_include_'+element).value; + } + populateCrsSelects(form,dirsel,filesel,1,include,1,0,1,1,0); + } + } + if (document.getElementById('chooser_'+element+'_upload')) { + var currcrsupload = document.getElementById('chooser_'+element+'_upload').style.display; + if (currcrsupload == 'none') { + dirsel = 'crsauthorpath_'+element; + filesel = ''; + populateCrsSelects(form,dirsel,filesel,0,'',1,0,1,0,1); + } + } } } - function toggleCrsFile(form,element,numdirs) { + function toggleCrsFile(form,element) { if (document.getElementById('chooser_'+element+'_crsres')) { var curr = document.getElementById('chooser_'+element+'_crsres').style.display; if (curr == 'none') { - if (numdirs) { + if (document.getElementById('coursepath_'+element)) { + var numdirs; + if (document.getElementById('coursepath_'+element).length) { + numdirs = document.getElementById('coursepath_'+element).length; + } + if ((document.getElementById('hascrsres_'+element)) && + (document.getElementById('nocrsres_'+element))) { + if (numdirs) { + document.getElementById('hascrsres_'+element).style.display='inline-block'; + document.getElementById('nocrsres_'+element).style.display='none'; + } else { + document.getElementById('hascrsres_'+element).style.display='none'; + document.getElementById('nocrsres_'+element).style.display='inline-block'; + } + } form.elements['coursepath_'+element].selectedIndex = 0; if (numdirs > 1) { - window['select1'+element+'_changed'](); + var selelem = form.elements['coursefile_'+element]; + var i, len = selelem.options.length -1; + if (len >=0) { + for (i = len; i >= 0; i--) { + selelem.remove(i); + } + selelem.options[0] = new Option('',''); + } } } - } + } document.getElementById('chooser_'+element+'_crsres').style.display = 'block'; - } if (document.getElementById('chooser_'+element+'_upload')) { document.getElementById('chooser_'+element+'_upload').style.display = 'none'; @@ -1857,20 +1915,20 @@ sub colorfuleditor_js { return; } - function toggleCrsUpload(form,element,numcrsdirs) { + function toggleCrsUpload(form,element) { if (document.getElementById('chooser_'+element+'_crsres')) { document.getElementById('chooser_'+element+'_crsres').style.display = 'none'; } if (document.getElementById('chooser_'+element+'_upload')) { var curr = document.getElementById('chooser_'+element+'_upload').style.display; if (curr == 'none') { - if (numcrsdirs) { - form.elements['crsauthorpath_'+element].selectedIndex = 0; - form.elements['newsubdir_'+element][0].checked = true; - toggleNewsubdir(form,element); + form.elements['newsubdir_'+element][0].checked = true; + toggleNewsubdir(form,element); + document.getElementById('chooser_'+element+'_upload').style.display = 'block'; + if (document.getElementById('uploadcrsres_'+element)) { + document.getElementById('uploadcrsres_'+element).value = ''; } } - document.getElementById('chooser_'+element+'_upload').style.display = 'block'; } return; } @@ -1914,19 +1972,21 @@ sub colorfuleditor_js { var filename = form.elements['coursefile_'+element]; var path = directory.options[directory.selectedIndex].value; var file = filename.options[filename.selectedIndex].value; - form.elements[element].value = '$respath'; - if (path == '/') { - form.elements[element].value += file; - } else { - form.elements[element].value += path+'/'+file; - } - unClean(); - if (document.getElementById('previewimg_'+element)) { - document.getElementById('previewimg_'+element).src = form.elements[element].value; - var newsrc = document.getElementById('previewimg_'+element).src; - } - if (document.getElementById('showimg_'+element)) { - document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})'; + if (file != '') { + form.elements[element].value = '$respath'; + if (path == '/') { + form.elements[element].value += file; + } else { + form.elements[element].value += path+'/'+file; + } + unClean(); + if (document.getElementById('previewimg_'+element)) { + document.getElementById('previewimg_'+element).src = form.elements[element].value; + var newsrc = document.getElementById('previewimg_'+element).src; + } + if (document.getElementById('showimg_'+element)) { + document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})'; + } } toggleChooser(form,element); return; @@ -2215,111 +2275,168 @@ sub crsauthor_url { } sub import_crsauthor_form { - my ($form,$firstselectname,$secondselectname,$onchangefirst,$only,$suffix,$disabled) = @_; + my ($firstselectname,$secondselectname,$onchangefirst,$only,$suffix,$disabled) = @_; return (0) unless ($env{'request.course.id'}); my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $crshome = $env{'course.'.$env{'request.course.id'}.'.home'}; return (0) unless (($cnum ne '') && ($cdom ne '')); - my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; my @ids=&Apache::lonnet::current_machine_ids(); - my ($output,$is_home,$relpath,%subdirs,%files,%selimport_menus); - + my ($output,$is_home,$toppath,%subdirs,%files,%selimport_menus,$include,$exclude); + if (grep(/^\Q$crshome\E$/,@ids)) { $is_home = 1; } - $relpath = "/priv/$cdom/$cnum"; - &Apache::lonnet::recursedirs($is_home,'priv',$londocroot,$relpath,'',\%subdirs,\%files); + $toppath = "/priv/$cdom/$cnum"; + my $nonemptydir = 1; + my $js_only; + if ($only) { + map { $include->{$_} = 1; } split(/\s*,\s*/,$only); + $js_only = join(',',map { &js_escape($_); } sort(keys(%{$include}))); + } + $exclude = &Apache::lonnet::priv_exclude(); + &Apache::lonnet::recursedirs($is_home,1,$include,$exclude,1,0,$toppath,'',\%subdirs,\%files); + my $numdirs = scalar(keys(%files)); my %lt = &Apache::lonlocal::texthash ( fnam => 'Filename', dire => 'Directory', + se => 'Select', ); - my $numdirs = scalar(keys(%files)); - my (%possexts,$singledir,@singledirfiles); - if ($only) { - map { $possexts{$_} = 1; } split(/\s*,\s*/,$only); - } - my (%nonemptydirs,$possdirs); - if ($numdirs > 1) { - my @order; - foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) { - if (ref($files{$key}) eq 'HASH') { - my $shown = $key; - if ($key eq '') { - $shown = '/'; - } - my @ordered = (); - foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$key}}))) { - next if ($file =~ /\.rights$/); - if ($only) { - my ($ext) = ($file =~ /\.([^.]+)$/); - unless ($possexts{lc($ext)}) { - next; - } + $output = $lt{'dire'}.': '. + '<select id="'.$firstselectname.'" name="'.$firstselectname.'" '. + 'onchange="populateCrsSelects(this.form,'."'$firstselectname','$secondselectname',1,'$js_only',0,1,0,0,0".');">'. + '<option value="" selected="selected">'.$lt{'se'}.'</option>'; + if ($files{'/'}) { + $output .= '<option value="/">/</option>'."\n"; + } + foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) { + next if ($key eq '/'); + $output .= '<option value="'.$key.'">'.$key.'</option>'."\n"; + } + $output .= '</select><br />'."\n". + $lt{'fnam'}.': <select id="'.$secondselectname.'" name="'.$secondselectname.'">'."\n". + '<option value="" selected="selected"></option>'."\n". + '</select>'."\n". + '<input type="hidden" id="crsres_include_'.$suffix.'" value="'.$only.'" />'; + return ($numdirs,$output); +} + +sub show_crsfiles_js { + my $excluderef = &Apache::lonnet::priv_exclude(); + my $se = &js_escape(&mt('Select')); + my $exclude; + if (ref($excluderef) eq 'HASH') { + $exclude = join(',', map { &js_escape($_); } sort(keys(%{$excluderef}))); + } + my $js = <<"END"; + + + function populateCrsSelects (form,dirsel,filesel,exc,include,setdir,setfile,recurse,nonemptydir,addtopdir) { + var relpath = ''; + if ((setfile) && (dirsel != null) && (dirsel != 'undefined') && (dirsel != '')) { + var currdir = form.elements[dirsel].options[form.elements[dirsel].selectedIndex].value; + if (currdir == '') { + if ((filesel != null) && (filesel != 'undefined') && (filesel != '')) { + selelem = form.elements[filesel]; + var j, numfiles = selelem.options.length -1; + if (numfiles >=0) { + for (j = numfiles; j >= 0; j--) { + selelem.remove(j); + } + } + if (selelem.options.length == 0) { + selelem.options[selelem.options.length] = new Option('',''); + selelem.selectedIndex = 0; } - $selimport_menus{$key}->{'select2'}->{$file} = $file; - push(@ordered,$file); - } - if (@ordered) { - push(@order,$key); - $nonemptydirs{$key} = 1; - $selimport_menus{$key}->{'text'} = $shown; - $selimport_menus{$key}->{'default'} = ''; - $selimport_menus{$key}->{'select2'}->{''} = ''; - $selimport_menus{$key}->{'order'} = \@ordered; } + return; + } else { + relpath = encodeURIComponent(form.elements[dirsel].options[form.elements[dirsel].selectedIndex].value); } } - $possdirs = scalar(keys(%nonemptydirs)); - if ($possdirs > 1) { - my @order = sort { lc($a) cmp lc($b) } (keys(%nonemptydirs)); - $output = $lt{'dire'}. - &linked_select_forms($form,'<br />'. - $lt{'fnam'},'', - $firstselectname,$secondselectname, - \%selimport_menus,\@order, - $onchangefirst,'',$suffix).'<br />'; - } elsif ($possdirs == 1) { - $singledir = (keys(%nonemptydirs))[0]; - if (ref($selimport_menus{$singledir}->{'order'}) eq 'ARRAY') { - @singledirfiles = @{$selimport_menus{$singledir}->{'order'}}; - } - delete($selimport_menus{$singledir}); - } - } elsif ($numdirs == 1) { - $singledir = (keys(%files))[0]; - foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$singledir}}))) { - if ($only) { - my ($ext) = ($file =~ /\.([^.]+)$/); - unless ($possexts{lc($ext)}) { - next; + var http = new XMLHttpRequest(); + var url = "/adm/courseauthor"; + var crsrole = "$env{'request.role'}"; + var exclude = ''; + if (exc) { + exclude = '$exclude'; + } + var params = "role=course&files=1&rec="+recurse+"&nonempty="+nonemptydir+"&exc="+exclude+"&inc="+include+"&addtop="+addtopdir+"&path="+relpath; + http.open("POST", url, true); + http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + http.onreadystatechange = function() { + if (http.readyState == 4 && http.status == 200) { + var data = JSON.parse(http.responseText); + var selelem; + if ((setdir) && (dirsel != null) && (dirsel != 'undefined') && (dirsel != '')) { + if (Array.isArray(data.dirs)) { + selelem = form.elements[dirsel]; + var i, numdirs = selelem.options.length -1; + if (numdirs >=0) { + for (i = numdirs; i >= 0; i--) { + selelem.remove(i); + } + } + var len = data.dirs.length; + if (len) { + selelem.options[selelem.options.length] = new Option('$se',''); + var j; + for (j = 0; j < len; j++) { + selelem.options[selelem.options.length] = new Option(data.dirs[j],data.dirs[j]); + } + selelem.selectedIndex = 0; + } + if (!setfile) { + if ((filesel != null) && (filesel != 'undefined') && (filesel != '')) { + selelem = form.elements[filesel]; + var j, numfiles = selelem.options.length -1; + if (numfiles >=0) { + for (j = numfiles; j >= 0; j--) { + selelem.remove(j); + } + } + if (selelem.options.length == 0) { + selelem.options[selelem.options.length] = new Option('',''); + selelem.selectedIndex = 0; + } + } + } + } + } + if ((setfile) && (filesel != null) && (filesel != 'undefined') && (filesel != '')) { + selelem = form.elements[filesel]; + var i, numfiles = selelem.options.length -1; + if (numfiles >=0) { + for (i = numfiles; i >= 0; i--) { + selelem.remove(i); + } + } + var x; + for (x in data.files) { + if (Array.isArray(data.files[x])) { + if (data.files[x].length > 1) { + selelem.options[selelem.options.length] = new Option('$se',''); + } + var len = data.files[x].length; + if (len) { + var k; + for (k = 0; k < len; k++) { + selelem.options[selelem.options.length] = new Option(data.files[x][k],data.files[x][k]); + } + selelem.selectedIndex = 0; + } + } + } + if (selelem.options.length == 0) { + selelem.options[selelem.options.length] = new Option('',''); + selelem.selectedIndex = 0; + } } - } else { - next if ($file =~ /\.rights$/); } - push(@singledirfiles,$file); - } - if (@singledirfiles) { - $possdirs = 1; - } - } - if (($possdirs == 1) && (@singledirfiles)) { - my $showdir = $singledir; - if ($singledir eq '') { - $showdir = '/'; } - $output = $lt{'dire'}. - '<select name="'.$firstselectname.'">'. - '<option value="'.$singledir.'">'.$showdir.'</option>'."\n". - '</select><br />'. - $lt{'fnam'}.'<select name="'.$secondselectname.'">'."\n". - '<option value="" selected="selected">'.$lt{'se'}.'</option>'."\n"; - foreach my $file (@singledirfiles) { - $output .= '<option value="'.$file.'">'.$file.'</option>'."\n"; - } - $output .= '</select><br />'."\n"; + http.send(params); } - return ($possdirs,$output); +END } =pod @@ -2671,7 +2788,7 @@ sub display_filter { my $onchange = "javascript:toggleHistoryOptions(this,'containingphrase','$context', '$secondid','$thirdid')"; return '<span class="LC_nobreak"><label>'.&mt('Records: [_1]', - &Apache::lonmeta::selectbox('show',$env{'form.show'},undef, + &Apache::lonmeta::selectbox('show',$env{'form.show'},'',undef, (&mt('all'),10,20,50,100,1000,10000))). '</label></span> <span class="LC_nobreak">'. &mt('Filter: [_1]', @@ -4284,6 +4401,30 @@ sub syllabuswrapper { # ----------------------------------------------------------------------------- +sub aboutme_on { + my ($uname,$udom)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $hashkey=$uname.':'.$udom; + my ($aboutme,$cached)=&Apache::lonnet::is_cached_new('aboutme',$hashkey); + if ($cached) { + return $aboutme; + } + $aboutme = &Apache::lonnet::usertools_access($uname,$udom,'aboutme'); + &Apache::lonnet::do_cache_new('aboutme',$hashkey,$aboutme,3600); + return $aboutme; +} + +sub devalidate_aboutme_cache { + my ($uname,$udom)=@_; + if (!$udom) { $udom =$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $id=$uname.':'.$udom; + &Apache::lonnet::devalidate_cache_new('aboutme',$id); +} + sub track_student_link { my ($linktext,$sname,$sdom,$target,$start,$only_body) = @_; my $link ="/adm/trackstudent?"; @@ -6235,6 +6376,8 @@ Input: (optional) filename from which br If page header is being requested for use in a frameset, then the second (option) argument -- frameset will be true, and the target attribute set for links should be target="_parent". + If $title is supplied as the thitd arg, that will be used to + the left of the breadcrumbs tail for the current path. Returns: HTML div with CSTR path and recent box To be included on Authoring Space pages @@ -6242,7 +6385,7 @@ Returns: HTML div with CSTR path and rec =cut sub CSTR_pageheader { - my ($trailfile,$frameset) = @_; + my ($trailfile,$frameset,$title) = @_; if ($trailfile eq '') { $trailfile = $env{'request.filename'}; } @@ -6265,13 +6408,15 @@ sub CSTR_pageheader { $lastitem = $thisdisfn; } - my ($crsauthor,$title); + my $crsauthor; if (($env{'request.course.id'}) && ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) && ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) { $crsauthor = 1; - $title = &mt('Course Authoring Space'); - } else { + if ($title eq '') { + $title = &mt('Course Authoring Space'); + } + } elsif ($title eq '') { $title = &mt('Authoring Space'); } @@ -6316,6 +6461,73 @@ sub CSTR_pageheader { return $output; } +sub nocodemirror { + my $nocodem = $env{'environment.nocodemirror'}; + unless ($nocodem) { + my %domdefs = &Apache::lonnet::get_domain_defaults($env{'user.domain'}); + if ($domdefs{'nocodemirror'}) { + $nocodem = 'yes'; + } + } + if ($nocodem eq 'yes') { + return 1; + } + return; +} + +sub permitted_editors { + my ($is_author,$is_coauthor,$auname,$audom,%editors); + if ($env{'request.role'} =~ m{^au\./}) { + $is_author = 1; + } elsif ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)}) { + ($audom,$auname) = ($1,$2); + if (($audom ne '') && ($auname ne '')) { + if (($env{'user.domain'} eq $audom) && + ($env{'user.name'} eq $auname)) { + $is_author = 1; + } else { + $is_coauthor = 1; + } + } + } elsif ($env{'request.course.id'}) { + if ($env{'request.editurl'} =~ m{^/priv/($match_domain)/($match_username)/}) { + ($audom,$auname) = ($1,$2); + } elsif ($env{'request.uri'} =~ m{^/priv/($match_domain)/($match_username)/}) { + ($audom,$auname) = ($1,$2); + } + if (($audom ne '') && ($auname ne '')) { + if (($env{'user.domain'} eq $audom) && + ($env{'user.name'} eq $auname)) { + $is_author = 1; + } else { + $is_coauthor = 1; + } + } + } + if ($is_author) { + if (exists($env{'environment.editors'})) { + map { $editors{$_} = 1; } split(/,/,$env{'environment.editors'}); + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + } elsif ($is_coauthor) { + if (exists($env{"environment.internal.editors./$audom/$auname"})) { + map { $editors{$_} = 1; } split(/,/,$env{"environment.internal.editors./$audom/$auname"}); + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + return %editors; +} + ############################################### ############################################### @@ -6710,6 +6922,9 @@ ENDJS $endbodytag; } } + if ((ref($args) eq 'HASH') && ($args->{'dashjs'})) { + $endbodytag = &Apache::lonhtmlcommon::dash_to_minus_js().$endbodytag; + } return $endbodytag; } @@ -8026,6 +8241,11 @@ fieldset { /* overflow: hidden; */ } +fieldset#LC_selectuser { + margin: 0; + padding: 0; +} + article.geogebraweb div { margin: 0; } @@ -9625,6 +9845,50 @@ sub symb_from_tinyurl { } } +sub usable_exttools { + my %tooltypes; + if ($env{'request.course.id'}) { + if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'}) { + if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'both') { + %tooltypes = ( + crs => 1, + dom => 1, + ); + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'crs') { + $tooltypes{'crs'} = 1; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'dom') { + $tooltypes{'dom'} = 1; + } + } else { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $crstype = lc($env{'course.'.$env{'request.course.id'}.'.type'}); + if ($crstype eq '') { + $crstype = 'course'; + } + if ($crstype eq 'course') { + if ($env{'course.'.$env{'request.course.id'}.'internal.coursecode'}) { + $crstype = 'official'; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.textbook'}) { + $crstype = 'textbook'; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.lti'}) { + $crstype = 'lti'; + } else { + $crstype = 'unofficial'; + } + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom); + if ($domdefaults{$crstype.'domexttool'}) { + $tooltypes{'dom'} = 1; + } + if ($domdefaults{$crstype.'exttool'}) { + $tooltypes{'crs'} = 1; + } + } + } + return %tooltypes; +} + sub wishlist_window { return(<<'ENDWISHLIST'); <script type="text/javascript"> @@ -10313,6 +10577,16 @@ Scalar: 1 if 'Course' to be used, 0 othe ############################################### sub show_course { + my ($udom,$uname) = @_; + if (($udom ne '') && ($uname ne '')) { + if (($udom ne $env{'user.domain'}) || ($uname ne $env{'user.name'})) { + if (&Apache::lonnet::is_advanced_user($udom,$uname)) { + return 0; + } else { + return 1; + } + } + } my $course = !$env{'user.adv'}; if (!$env{'user.adv'}) { foreach my $env (keys(%env)) { @@ -14287,7 +14561,9 @@ sub process_extracted_files { my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'. $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'. $title; - if (($outer !~ /\D/) && ($mapinner{$outer} !~ /\D/) && ($newidx !~ /\D/)) { + if (($outer !~ /\D/) && + (($mapinner{$outer} eq 'default') || ($mapinner{$outer} !~ /\D/)) && + ($newidx !~ /\D/)) { if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") { mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755); } @@ -16342,27 +16618,33 @@ sub assign_category_rows { sub commit_customrole { - my ($udom,$uname,$url,$three,$four,$five,$start,$end,$context) = @_; + my ($udom,$uname,$url,$three,$four,$five,$start,$end,$context,$othdomby,$requester) = @_; + my $result = &Apache::lonnet::assigncustomrole( + $udom,$uname,$url,$three,$four,$five,$end,$start,undef,undef, + $context,$othdomby,$requester); my $output = &mt('Assigning custom role').' "'.$five.'" by '.$four.':'.$three.' in '.$url. ($start?', '.&mt('starting').' '.localtime($start):''). - ($end?', ending '.localtime($end):'').': <b>'. - &Apache::lonnet::assigncustomrole( - $udom,$uname,$url,$three,$four,$five,$end,$start,undef,undef,$context). - '</b><br />'; - return $output; + ($end?', ending '.localtime($end):'').': <b>'.$result.'</b><br />'; + if (wantarray) { + return ($output,$result); + } else { + return $output; + } } sub commit_standardrole { - my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits) = @_; - my ($output,$logmsg,$linefeed); + my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits, + $othdomby,$requester) = @_; + my ($output,$logmsg,$linefeed,$result); if ($context eq 'auto') { $linefeed = "\n"; } else { $linefeed = "<br />\n"; } if ($three eq 'st') { - my $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end, - $one,$two,$sec,$context,$credits); + $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end, + $one,$two,$sec,$context,$credits,$othdomby, + $requester); if (($result =~ /^error/) || ($result eq 'not_in_class') || ($result eq 'unknown_course') || ($result eq 'refused')) { $output = $logmsg.' '.&mt('Error: ').$result."\n"; @@ -16382,19 +16664,24 @@ sub commit_standardrole { $output = &mt('Assigning').' '.$three.' in '.$url. ($start?', '.&mt('starting').' '.localtime($start):''). ($end?', '.&mt('ending').' '.localtime($end):'').': '; - my $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start,'','',$context); + $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start, + '','',$context,$othdomby,$requester); if ($context eq 'auto') { $output .= $result.$linefeed; } else { $output .= '<b>'.$result.'</b>'.$linefeed; } } - return $output; + if (wantarray) { + return ($output,$result); + } else { + return $output; + } } sub commit_studentrole { my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context, - $credits) = @_; + $credits,$othdomby,$requester) = @_; my ($result,$linefeed,$oldsecurl,$newsecurl); if ($context eq 'auto') { $linefeed = "\n"; @@ -16418,8 +16705,9 @@ sub commit_studentrole { } $oldsecurl = $uurl; $expire_role_result = - &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,'','',$context); - if ($env{'request.course.sec'} ne '') { + &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now, + '','','',$context,$othdomby,$requester); + if ($env{'request.course.sec'} ne '') { if ($expire_role_result eq 'refused') { my @roles = ('st'); my @statuses = ('previous'); @@ -16445,7 +16733,8 @@ sub commit_studentrole { &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef, undef,undef,undef,$sec, $end,$start,'','',$cid, - '',$context,$credits); + '',$context,$credits,'', + $othdomby,$requester); if ($modify_section_result =~ /^ok/) { if ($secchange == 1) { if ($sec eq '') { @@ -16838,6 +17127,7 @@ sub construct_course { $cenv{'internal.defaultcredits'} = $args->{'defaultcredits'}; } my @badclasses = (); # Used to accumulate sections/crosslistings that did not pass classlist access check for course owner. + my @oklcsecs = (); # Used to accumulate LON-CAPA sections for validated institutional sections. if ($args->{'crssections'}) { $cenv{'internal.sectionnums'} = ''; if ($args->{'crssections'} =~ m/,/) { @@ -16851,7 +17141,11 @@ sub construct_course { my $class = $args->{'crscode'}.$sec; my $addcheck = &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$class,$cenv{'internal.courseowner'}); $cenv{'internal.sectionnums'} .= $item.','; - unless ($addcheck eq 'ok') { + if ($addcheck eq 'ok') { + unless (grep(/^\Q$gp\E$/,@oklcsecs)) { + push(@oklcsecs,$gp); + } + } else { push(@badclasses,$class); } } @@ -16879,7 +17173,11 @@ sub construct_course { my ($xl,$gp) = split/:/,$item; my $addcheck = &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$xl,$cenv{'internal.courseowner'}); $cenv{'internal.crosslistings'} .= $item.','; - unless ($addcheck eq 'ok') { + if ($addcheck eq 'ok') { + unless (grep(/^\Q$gp\E$/,@oklcsecs)) { + push(@oklcsecs,$gp); + } + } else { push(@badclasses,$xl); } } @@ -16942,6 +17240,36 @@ sub construct_course { if ($args->{'no_end_date'}) { $args->{'endaccess'} = 0; } +# If an official course with institutional sections is created by cloning +# an existing course, section-specific hiding of course totals in student's +# view of grades as copied from cloned course, will be checked for valid +# sections. + if (($can_clone && $cloneid) && + ($cenv{'internal.coursecode'} ne '') && + ($cenv{'grading'} eq 'standard') && + ($cenv{'hidetotals'} ne '') && + ($cenv{'hidetotals'} ne 'all')) { + my @hidesecs; + my $deletehidetotals; + if (@oklcsecs) { + foreach my $sec (split(/,/,$cenv{'hidetotals'})) { + if (grep(/^\Q$sec$/,@oklcsecs)) { + push(@hidesecs,$sec); + } + } + if (@hidesecs) { + $cenv{'hidetotals'} = join(',',@hidesecs); + } else { + $deletehidetotals = 1; + } + } else { + $deletehidetotals = 1; + } + if ($deletehidetotals) { + delete($cenv{'hidetotals'}); + &Apache::lonnet::del('environment',['hidetotals'],$$crsudom,$$crsunum); + } + } $cenv{'internal.autostart'}=$args->{'enrollstart'}; $cenv{'internal.autoend'}=$args->{'enrollend'}; $cenv{'default_enrollment_start_date'}=$args->{'startaccess'}; @@ -17060,7 +17388,6 @@ sub construct_course { # unless (($args->{'nonstandard'}) || ($args->{'firstres'} eq 'blank') || ($cloneid)) { - use LONCAPA::map; $outcome .= &mt('Setting first resource').': '; my $map = '/uploaded/'.$$crsudom.'/'.$$crsunum.'/default.sequence'; @@ -17303,7 +17630,8 @@ sub init_user_environment { my $public=($username eq 'public' && $domain eq 'public'); - my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv); + my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv, + $coauthorenv); my $now=time; if ($public) { @@ -17369,7 +17697,7 @@ sub init_user_environment { # Initialize roles - ($userroles,$firstaccenv,$timerintenv) = + ($userroles,$firstaccenv,$timerintenv,$coauthorenv) = &Apache::lonnet::rolesinit($domain,$username,$authhost); } # ------------------------------------ Check browser type and MathML capability @@ -17447,8 +17775,8 @@ sub init_user_environment { my %is_adv = ( is_adv => $env{'user.adv'} ); my %domdef = &Apache::lonnet::get_domain_defaults($domain); - foreach my $tool ('aboutme','blog','webdav','portfolio','timezone') { - $userenv{'availabletools.'.$tool} = + foreach my $tool ('aboutme','blog','webdav','portfolio','portaccess','timezone') { + $userenv{'availabletools.'.$tool} = &Apache::lonnet::usertools_access($username,$domain,$tool,'reload', undef,\%userenv,\%domdef,\%is_adv); } @@ -17460,6 +17788,17 @@ sub init_user_environment { \%userenv,\%domdef,\%is_adv); } + if ((ref($userroles) eq 'HASH') && ($userroles->{'user.author'}) && + (exists($userroles->{"user.role.au./$domain/"}))) { + if ($userenv{'authoreditors'}) { + $userenv{'editors'} = $userenv{'authoreditors'}; + } elsif ($domdef{'editors'} ne '') { + $userenv{'editors'} = $domdef{'editors'}; + } else { + $userenv{'editors'} = 'edit,xml'; + } + } + $userenv{'canrequest.author'} = &Apache::lonnet::usertools_access($username,$domain,'requestauthor', 'reload','requestauthor', @@ -17516,6 +17855,11 @@ sub init_user_environment { if (ref($timerintenv) eq 'HASH') { &_add_to_env(\%disk_env,$timerintenv); } + if (ref($coauthorenv) eq 'HASH') { + if (keys(%{$coauthorenv})) { + &_add_to_env(\%disk_env,$coauthorenv); + } + } if (ref($args->{'extra_env'})) { &_add_to_env(\%disk_env,$args->{'extra_env'}); } @@ -18320,30 +18664,54 @@ sub needs_coursereinit { if ($blocked) { return (); } - my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum); - if ($lastchange > $env{'request.course.tied'}) { - my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired'); - if ($curr_reqd_hash{'internal.releaserequired'} ne '') { - my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'}; - if ($curr_reqd_hash{'internal.releaserequired'} ne $required) { - &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' => - $curr_reqd_hash{'internal.releaserequired'}}); - my ($switchserver,$switchwarning) = - &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'}, - $curr_reqd_hash{'internal.releaserequired'}); - if ($switchwarning ne '' || $switchserver ne '') { - return ('switch',$switchwarning,$switchserver); - } + my $update; + my $lastmainchange = &Apache::lonnet::get_coursechange($cdom,$cnum); + my $lastsuppchange = &Apache::lonnet::get_suppchange($cdom,$cnum); + if ($lastmainchange > $env{'request.course.tied'}) { + my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum); + if ($needswitch) { + return ('switch',$switchwarning,$switchserver); + } + $update = 'main'; + } + if ($lastsuppchange > $env{'request.course.suppupdated'}) { + if ($update) { + $update = 'both'; + } else { + my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum); + if ($needswitch) { + return ('switch',$switchwarning,$switchserver); + } else { + $update = 'supp'; } } - return ('update'); + return ($update); + } + } + return (); +} + +sub switch_for_update { + my ($loncaparev,$cdom,$cnum) = @_; + my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired'); + if ($curr_reqd_hash{'internal.releaserequired'} ne '') { + my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'}; + if ($curr_reqd_hash{'internal.releaserequired'} ne $required) { + &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' => + $curr_reqd_hash{'internal.releaserequired'}}); + my ($switchserver,$switchwarning) = + &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'}, + $curr_reqd_hash{'internal.releaserequired'}); + if ($switchwarning ne '' || $switchserver ne '') { + return ('switch',$switchwarning,$switchserver); + } } } return (); } sub update_content_constraints { - my ($cdom,$cnum,$chome,$cid,$keeporder) = @_; + my ($cdom,$cnum,$chome,$cid) = @_; my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired'); my ($reqdmajor,$reqdminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'}); my (%checkresponsetypes,%checkcrsrestypes); @@ -18391,25 +18759,7 @@ sub update_content_constraints { } undef($navmap); } - my (@resources,@order,@resparms,@zombies); - if ($keeporder) { - use LONCAPA::map; - @resources = @LONCAPA::map::resources; - @order = @LONCAPA::map::order; - @resparms = @LONCAPA::map::resparms; - @zombies = @LONCAPA::map::zombies; - } - my $suppmap = 'supplemental.sequence'; - my ($suppcount,$supptools,$errors) = (0,0,0); - ($suppcount,$supptools,$errors) = &recurse_supplemental($cnum,$cdom,$suppmap, - $suppcount,$supptools,$errors); - if ($keeporder) { - @LONCAPA::map::resources = @resources; - @LONCAPA::map::order = @order; - @LONCAPA::map::resparms = @resparms; - @LONCAPA::map::zombies = @zombies; - } - if ($supptools) { + if (&Apache::lonnet::count_supptools($cnum,$cdom,1)) { my ($major,$minor) = split(/\./,$checkcrsrestypes{'exttool'}); if (($major > $reqdmajor) || ($major == $reqdmajor && $minor > $reqdminor)) { ($reqdmajor,$reqdminor) = ($major,$minor); @@ -18435,7 +18785,7 @@ sub allmaps_incourse { if ($lastchange > $env{'request.course.tied'}) { my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum"); unless ($ferr) { - &update_content_constraints($cdom,$cnum,$chome,$cid,1); + &update_content_constraints($cdom,$cnum,$chome,$cid); } } my $navmap = Apache::lonnavmaps::navmap->new(); @@ -18461,8 +18811,10 @@ sub parse_supplemental_title { my $name = &plainname($uname,$udom); $name = &HTML::Entities::encode($name,'"<>&\''); $renametitle = &HTML::Entities::encode($renametitle,'"<>&\''); - $title='<i>'.&Apache::lonlocal::locallocaltime($time).'</i> '. - $name.': <br />'.$foldertitle; + $title='<i>'.&Apache::lonlocal::locallocaltime($time).'</i> '.$name; + if ($foldertitle ne '') { + $title .= ': <br />'.$foldertitle; + } } if (wantarray) { return ($title,$foldertitle,$renametitle); @@ -18470,9 +18822,51 @@ sub parse_supplemental_title { return $title; } +sub get_supplemental { + my ($cnum,$cdom,$ignorecache,$possdel)=@_; + my $hashid=$cnum.':'.$cdom; + my ($supplemental,$cached,$set_httprefs); + unless ($ignorecache) { + ($supplemental,$cached) = &Apache::lonnet::is_cached_new('supplemental',$hashid); + } + unless (defined($cached)) { + my $chome=&Apache::lonnet::homeserver($cnum,$cdom); + unless ($chome eq 'no_host') { + my @order = @LONCAPA::map::order; + my @resources = @LONCAPA::map::resources; + my @resparms = @LONCAPA::map::resparms; + my @zombies = @LONCAPA::map::zombies; + my ($errors,%ids,%hidden); + $errors = + &recurse_supplemental($cnum,$cdom,'supplemental.sequence', + $errors,$possdel,\%ids,\%hidden); + @LONCAPA::map::order = @order; + @LONCAPA::map::resources = @resources; + @LONCAPA::map::resparms = @resparms; + @LONCAPA::map::zombies = @zombies; + $set_httprefs = 1; + if ($env{'request.course.id'} eq $cdom.'_'.$cnum) { + &Apache::lonnet::appenv({'request.course.suppupdated' => time}); + } + $supplemental = { + ids => \%ids, + hidden => \%hidden, + }; + &Apache::lonnet::do_cache_new('supplemental',$hashid,$supplemental,600); + } + } + return ($supplemental,$set_httprefs); +} + sub recurse_supplemental { - my ($cnum,$cdom,$suppmap,$numfiles,$numexttools,$errors) = @_; - if ($suppmap) { + my ($cnum,$cdom,$suppmap,$errors,$possdel,$suppids,$hiddensupp,$hidden) = @_; + if (($suppmap) && (ref($suppids) eq 'HASH') && (ref($hiddensupp) eq 'HASH')) { + my $mapnum; + if ($suppmap eq 'supplemental.sequence') { + $mapnum = 0; + } else { + ($mapnum) = ($suppmap =~ /^supplemental_(\d+)\.sequence$/); + } my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap); if ($fatal) { $errors ++; @@ -18480,24 +18874,95 @@ sub recurse_supplemental { my @order = @LONCAPA::map::order; if (@order > 0) { my @resources = @LONCAPA::map::resources; + my @resparms = @LONCAPA::map::resparms; foreach my $idx (@order) { my ($title,$src,$ext,$type,$status)=split(/\:/,$resources[$idx]); if (($src ne '') && ($status eq 'res')) { + my $id = $mapnum.':'.$idx; + push(@{$suppids->{$src}},$id); + if (($hidden) || (&get_supp_parameter($resparms[$idx],'parameter_hiddenresource') =~ /^yes/i)) { + $hiddensupp->{$id} = 1; + } if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) { - ($numfiles,$numexttools,$errors) = &recurse_supplemental($cnum,$cdom,$1, - $numfiles,$numexttools,$errors); + $errors = &recurse_supplemental($cnum,$cdom,$1,$errors,$possdel,$suppids, + $hiddensupp,$hiddensupp->{$id}); } else { - if ($src =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) { - $numexttools ++; + my $allowed; + if (($env{'request.role.adv'}) || (!$hiddensupp->{$id})) { + $allowed = 1; + } elsif ($possdel) { + foreach my $item (@{$suppids->{$src}}) { + next if ($item eq $id); + unless ($hiddensupp->{$item}) { + $allowed = 1; + last; + } + } + if ((!$allowed) && (exists($env{'httpref.'.$src}))) { + &Apache::lonnet::delenv('httpref.'.$src); + } + } + if ($allowed && (!exists($env{'httpref.'.$src}))) { + &Apache::lonnet::allowuploaded('/adm/coursedoc',$src); } - $numfiles ++; } } } } } } - return ($numfiles,$numexttools,$errors); + return $errors; +} + +sub set_supp_httprefs { + my ($cnum,$cdom,$supplemental,$possdel) = @_; + if (ref($supplemental) eq 'HASH') { + if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) { + foreach my $src (keys(%{$supplemental->{'ids'}})) { + next if ($src =~ /\.sequence$/); + if (ref($supplemental->{'ids'}->{$src}) eq 'ARRAY') { + my $allowed; + if ($env{'request.role.adv'}) { + $allowed = 1; + } else { + foreach my $id (@{$supplemental->{'ids'}->{$src}}) { + unless ($supplemental->{'hidden'}->{$id}) { + $allowed = 1; + last; + } + } + } + if (exists($env{'httpref.'.$src})) { + if ($possdel) { + unless ($allowed) { + &Apache::lonnet::delenv('httpref.'.$src); + } + } + } elsif ($allowed) { + &Apache::lonnet::allowuploaded('/adm/coursedoc',$src); + } + } + } + if ($env{'request.course.id'} eq $cdom.'_'.$cnum) { + &Apache::lonnet::appenv({'request.course.suppupdated' => time}); + } + } + } +} + +sub get_supp_parameter { + my ($resparm,$name)=@_; + return if ($resparm eq ''); + my $value=undef; + my $ptype=undef; + foreach (split('&&&',$resparm)) { + my ($thistype,$thisname,$thisvalue)=split('___',$_); + if ($thisname eq $name) { + $value=$thisvalue; + $ptype=$thistype; + } + } + return $value; } sub symb_to_docspath { @@ -18570,6 +19035,67 @@ sub symb_to_docspath { return $path; } +sub validate_folderpath { + my ($supplementalflag,$allowed,$coursenum,$coursedom) = @_; + if ($env{'form.folderpath'} ne '') { + my @items = split(/\&/,$env{'form.folderpath'}); + my ($badpath,$changed,$got_supp,$supppath,%supphidden,%suppids); + for (my $i=0; $i<@items; $i++) { + my $odd = $i%2; + if (($odd) && (!$supplementalflag) && ($items[$i] !~ /^[^:]*:(|\d+):(|1):(|1):(|1):(|1)$/)) { + $badpath = 1; + } elsif ($odd && $supplementalflag) { + my $idx = $i-1; + if ($items[$i] =~ /^([^:]*)::(|1):::$/) { + my $esc_name = $1; + if ((!$allowed) || ($items[$idx] eq 'supplemental')) { + $supppath .= '&'.$esc_name; + $changed = 1; + } else { + $supppath .= '&'.$items[$i]; + } + } elsif (($allowed) && ($items[$idx] ne 'supplemental')) { + $changed = 1; + my $is_hidden; + unless ($got_supp) { + my ($supplemental) = &get_supplemental($coursenum,$coursedom); + if (ref($supplemental) eq 'HASH') { + if (ref($supplemental->{'hidden'}) eq 'HASH') { + %supphidden = %{$supplemental->{'hidden'}}; + } + if (ref($supplemental->{'ids'}) eq 'HASH') { + %suppids = %{$supplemental->{'ids'}}; + } + } + $got_supp = 1; + } + if (ref($suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}) eq 'ARRAY') { + my $mapid = $suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}->[0]; + if ($supphidden{$mapid}) { + $is_hidden = 1; + } + } + $supppath .= '&'.$items[$i].'::'.$is_hidden.':::'; + } else { + $supppath .= '&'.$items[$i]; + } + } elsif ((!$odd) && ($items[$i] !~ /^(default|supplemental)(|_\d+)$/)) { + $badpath = 1; + } elsif ($supplementalflag) { + $supppath .= '&'.$items[$i]; + } + last if ($badpath); + } + if ($badpath) { + delete($env{'form.folderpath'}); + } elsif ($changed && $supplementalflag) { + $supppath =~ s/^\&//; + $env{'form.folderpath'} = $supppath; + } + } + return; +} + sub captcha_display { my ($context,$lonhost,$defdom) = @_; my ($output,$error);