--- loncom/interface/slotrequest.pm 2013/01/23 17:48:37 1.117 +++ loncom/interface/slotrequest.pm 2019/06/22 19:07:58 1.140 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler for requesting to have slots added to a students record # -# $Id: slotrequest.pm,v 1.117 2013/01/23 17:48:37 bisitz Exp $ +# $Id: slotrequest.pm,v 1.140 2019/06/22 19:07:58 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -37,7 +37,7 @@ use Apache::lonnet; use Apache::lonnavmaps(); use Date::Manip; use lib '/home/httpd/lib/perl/'; -use LONCAPA; +use LONCAPA qw(:DEFAULT :match); sub fail { my ($r,$code)=@_; @@ -56,12 +56,22 @@ sub fail { } sub start_page { - my ($r,$title,$brcrum)=@_; + my ($r,$title,$brcrum,$js)=@_; my $args; if (ref($brcrum) eq 'ARRAY') { $args = {bread_crumbs => $brcrum}; } - $r->print(&Apache::loncommon::start_page($title,undef,$args)); + if (($env{'form.requestattempt'}) || ($env{'form.command'} eq 'manageresv')) { + my %loaditems = ( + onload => 'javascript:uncheckSlotRadio();', + ); + if (ref($args) eq 'HASH') { + $args->{'add_entries'} = \%loaditems; + } else { + $args = { 'add_entries' => \%loaditems }; + } + } + $r->print(&Apache::loncommon::start_page($title,$js,$args)); } sub end_page { @@ -69,6 +79,291 @@ sub end_page { $r->print(&Apache::loncommon::end_page()); } +sub reservation_js { + my ($slots,$consumed_uniqueperiods,$available,$got_slots,$symb) = @_; + return unless ((ref($slots) eq 'HASH') && (ref($available) eq 'ARRAY')); + my $toskip; + if ($symb eq '') { + $toskip = { symb => 1, }; + } + my ($i,$j) = (0,0); + my $js; + foreach my $slot (sort + { return $slots->{$a}->{'starttime'} <=> $slots->{$b}->{'starttime'} } + (keys(%{$slots}))) { + + next if (!&allowed_slot($slot,$slots->{$slot},$symb,$slots, + $consumed_uniqueperiods,$toskip)); + $js .= " slotstart[$i]='$slots->{$slot}->{'starttime'}';\n". + " slotend[$i]='$slots->{$slot}->{'endtime'}';\n". + " slotname[$i]='$slot';\n"; + if (($symb) && (ref($got_slots) eq 'ARRAY')) { + if (grep(/^\Q$slot\E$/,@{$got_slots})) { + $js .= " currslot[$j]='$slot';\n"; + $j++; + } + } + $i++; + push(@{$available},$slot); + } + if ($j) { + $js = " var currslot = new Array($j);\n\n$js"; + } + my %alerts = &Apache::lonlocal::texthash ( + none => 'No reservable time slots found', + invalid => 'Invalid date format', + ); + return <<"ENDSCRIPT"; +<script type="text/javascript"> +// <![CDATA[ +function updateSlotDisplay(form,num,slotpickradio) { + var slotstart = new Array($i); + var slotend = new Array($i); + var slotname = new Array($i); +$js + + if (slotpickradio == 'all') { + for (var i=0; i<$i; i++) { + if (document.getElementById('LC_slotrow_'+num+'_'+slotname[i])) { + document.getElementById('LC_slotrow_'+num+'_'+slotname[i]).style.display = ''; + } + if (document.getElementById('LC_slotsearch_'+num)) { + document.getElementById('LC_slotsearch_'+num).style.display = 'block'; + } + } + } else { + if (slotpickradio == 'show') { + for (var i=0; i<$i; i++) { + if (document.getElementById('LC_slotrow_'+num+'_'+slotname[i])) { + document.getElementById('LC_slotrow_'+num+'_'+slotname[i]).style.display = 'none'; + } + } + for (var j=0; j<$j; j++) { + if (document.getElementById('LC_slotrow_'+num+'_'+currslot[j])) { + document.getElementById('LC_slotrow_'+num+'_'+currslot[j]).style.display = ''; + } + } + if (document.getElementById('LC_slotsearch_'+num)) { + document.getElementById('LC_slotsearch_'+num).style.display = 'block'; + } + } else { + var numberRegExp = /^[0-9]+\$/; + var startm = form.start_month.options[form.start_month.selectedIndex].value; + var startd = form.start_day.value; + startd=startd.trim(); + var starty = form.start_year.value; + starty=starty.trim(); + var endm = form.end_month.options[form.end_month.selectedIndex].value; + var endd = form.end_day.value; + endd=endd.trim(); + var endy = form.end_year.value; + endy=endy.trim(); + if (numberRegExp.test(endd) && numberRegExp.test(endy) && numberRegExp.test(startd) && numberRegExp.test(starty)) { + var startdate = startm+"/"+startd+"/"+starty; + var starttime = new Date(startdate).getTime(); + starttime = starttime/1000; + var starth = form.start_hour.options[form.start_hour.selectedIndex].value; + if (numberRegExp.test(starth)) { + starth = parseInt(starth); + if (starth > 0 && starth <= 23) { + starttime += 3600 * starth; + } + } + var enddate = endm+"/"+endd+"/"+endy; + var endtime = new Date(enddate).getTime(); + endtime = endtime/1000; + var endh = form.end_hour.options[form.end_hour.selectedIndex].value; + if (numberRegExp.test(endh)) { + endh = parseInt(endh); + if (endh > 0 && endh <= 23) { + endtime += 3600 * endh; + } + } + + var shown = 0; + for (var i=0; i<$i; i++) { + if ((slotstart[i] >= starttime) && (slotend[i] <= endtime)) { + if (document.getElementById('LC_slotrow_'+num+'_'+slotname[i])) { + document.getElementById('LC_slotrow_'+num+'_'+slotname[i]).style.display = ''; + shown ++; + } + } else { + if (document.getElementById('LC_slotrow_'+num+'_'+slotname[i])) { + document.getElementById('LC_slotrow_'+num+'_'+slotname[i]).style.display = 'none'; + } + } + } + if (document.getElementById('LC_slotsearch_'+num)) { + if (shown) { + document.getElementById('LC_slotsearch_'+num).style.display = 'block'; + } else { + document.getElementById('LC_slotsearch_'+num).style.display = 'none'; + } + } + if (shown == 0) { + alert('$alerts{"none"}'); + } + } else { + alert('$alerts{"invalid"}'); + } + } + } + return; +} + +function toggleSlotDisplay(form,num) { + if (form.slotpick.length) { + for (var i=0; i<form.slotpick.length; i++) { + if (form.slotpick[i].checked) { + var val = form.slotpick[i].value; + if (document.getElementById('LC_slotfilter_'+num)) { + document.getElementById('LC_slotsearch_'+num).style.display = 'none'; + if (val == 'filter') { + document.getElementById('LC_slotfilter_'+num).style.display = 'block'; + } else { + document.getElementById('LC_slotfilter_'+num).style.display = 'none'; + if (val == 'all') { + updateSlotDisplay(form,num,val); + } else { + updateSlotDisplay(form,num,val); + } + } + } + break; + } + } + } + return false; +} + +if (!document.getElementsByClassName) { + function getElementsByClassName(node, classname) { + var a = []; + var re = new RegExp('(^| )'+classname+'( |$)'); + var els = node.getElementsByTagName("*"); + for(var i=0,j=els.length; i<j; i++) + if(re.test(els[i].className))a.push(els[i]); + return a; + } +} + +function uncheckSlotRadio() { + var slotpicks; + if (document.getElementsByClassName) { + slotpicks = document.getElementsByClassName('LC_slotpick_radio'); + } else { + slotpicks = getElementsByClassName(document.body,'LC_slotpick_radio'); + } + if (slotpicks.length) { + for (var i=0; i<slotpicks.length; i++) { + slotpicks[i].checked = false; + } + } +} + +function toggleSlotMap(maprownum,rownum) { + if (document.getElementById('arrow'+maprownum)) { + var img = document.getElementById('arrow'+maprownum); + var rowdisplay; + var celldisplay = ''; + if (img.src.indexOf('arrow.open.gif')!=-1) { + img.src = '/adm/lonIcons/arrow.closed.gif'; + rowdisplay = 'none'; + } + else { + img.src = '/adm/lonIcons/arrow.open.gif'; + rowdisplay = 'table-row'; + celldisplay = 'none'; + } + var resrows; + var maptext; + if (document.getElementsByClassName) { + resrows = document.getElementsByClassName('LC_slotresrow_'+maprownum); + maptext = document.getElementsByClassName('LC_slotmaptext_'+maprownum); + } else { + resrows = getElementsByClassName(document.body,'LC_slotresrow_'+maprownum); + maptext = getElementsByClassName(document.body,'LC_slotmaptext_'+maprownum); + } + if (maptext.length) { + for (var i=0; i<maptext.length; i++) { + maptext[i].style.display = celldisplay; + } + } + if (resrows.length) { + var mapbgidx = 0; + var rowclasses = ['LC_even_row','LC_odd_row']; + var mapbgClass = 'LC_even_row'; + var regExpBg = /LC_odd_row/i; + if (rowdisplay == 'table-row') { + if (document.getElementById('LC_slotmaprow_'+rownum)) { + mapbgClass = document.getElementById('LC_slotmaprow_'+rownum).className; + if (regExpBg.test(mapbgClass)) { + mapbgidx = 1; + } + } + } + for (var i=0; i<resrows.length; i++) { + resrows[i].style.display = rowdisplay; + if (rowdisplay == 'table-row') { + mapbgidx ++; + var bgcolnew = mapbgidx % 2; + var bgcolold = (mapbgidx+1) % 2; + var k = i+parseInt(rownum)+1; + if (document.getElementById('LC_slotresrow_'+k)) { + document.getElementById('LC_slotresrow_'+k).className = document.getElementById('LC_slotresrow_'+k).className.replace(rowclasses[bgcolold],rowclasses[bgcolnew]); + } + } + } + if (document.getElementById('LC_slot_reservations')) { + var numrowsOdd = resrows.length % 2; + if (numrowsOdd) { + var lastbgClass = 'LC_even_row'; + var idx = 0; + var lastresnum = parseInt(rownum) + resrows.length; + if (rowdisplay == 'none') { + lastresnum = rownum; + if (document.getElementById('LC_slotmaprow_'+rownum)) { + lastbgClass = document.getElementById('LC_slotmaprow_'+rownum).className; + } + } else { + lastresnum = parseInt(rownum) + resrows.length; + if (document.getElementById('LC_slotresrow_'+lastresnum)) { + lastbgClass = document.getElementById('LC_slotresrow_'+lastresnum).className; + } + } + if (regExpBg.test(lastbgClass)) { + idx = 1; + } + var table = document.getElementById('LC_slot_reservations'); + if ((table.rows.length) && (table.rows.length >= lastresnum)) { + for (var i=lastresnum; i<table.rows.length; i++) { + if (table.rows[i].style.display != 'none') { + idx ++; + var bgcolnew = idx % 2; + var bgcolold = (idx+1) % 2; + j = i+1; + if (document.getElementById('LC_slotmaprow_'+j)) { + document.getElementById('LC_slotmaprow_'+j).className = rowclasses[bgcolnew]; + } else { + if (document.getElementById('LC_slotresrow_'+j)) { + document.getElementById('LC_slotresrow_'+j).className = document.getElementById('LC_slotresrow_'+j).className.replace(rowclasses[bgcolold],rowclasses[bgcolnew]); + } + } + } + } + } + } + } + } + } +} +// ]]> +</script> +ENDSCRIPT + +} + + =pod slot_reservations db @@ -121,7 +416,6 @@ sub check_for_reservation { my ($symb,$mode)=@_; my $student = &Apache::lonnet::EXT("resource.0.availablestudent", $symb, $env{'user.domain'}, $env{'user.name'}); - my $course = &Apache::lonnet::EXT("resource.0.available", $symb, $env{'user.domain'}, $env{'user.name'}); my @slots = (split(/:/,$student), split(/:/, $course)); @@ -171,7 +465,7 @@ sub get_consumed_uniqueperiods { return 'error: Unable to determine current status'; } my @problems = $navmap->retrieveResources(undef, - sub { $_[0]->is_problem() },1,0); + sub { $_[0]->is_problem() || $_[0]->is_tool() },1,0); my %used_slots; foreach my $problem (@problems) { my $symb = $problem->symb(); @@ -218,16 +512,19 @@ sub check_for_conflict { if (!defined($new_slot->{'uniqueperiod'})) { return undef; } if (!ref($consumed_uniqueperiods)) { - $consumed_uniqueperiods = &get_consumed_uniqueperiods($slots); - if (ref($consumed_uniqueperiods) eq 'HASH') { - if (&Apache::lonnet::error(%$consumed_uniqueperiods)) { - return 'error: Unable to determine current status'; - } + if ($consumed_uniqueperiods =~ /^error: /) { + return $consumed_uniqueperiods; } else { - return 'error: Unable to determine current status'; + $consumed_uniqueperiods = &get_consumed_uniqueperiods($slots); + if (ref($consumed_uniqueperiods) eq 'HASH') { + if (&Apache::lonnet::error(%$consumed_uniqueperiods)) { + return 'error: Unable to determine current status'; + } + } else { + return 'error: Unable to determine current status'; + } } - } - + } my ($new_uniq_start,$new_uniq_end) = @{$new_slot->{'uniqueperiod'}}; foreach my $slot_name (keys(%$consumed_uniqueperiods)) { my ($start,$end)=@{$consumed_uniqueperiods->{$slot_name}}; @@ -504,14 +801,20 @@ sub release_reservation { return (0,'error: Unable to determine current status'); } my $passed_resource = $navmap->getBySymb($symb); - if ($passed_resource->is_map()) { - my ($a_resource) = - $navmap->retrieveResources($passed_resource, - sub {$_[0]->is_problem()},0,1); - $symb = $a_resource->symb(); + if (ref($passed_resource)) { + if ($passed_resource->is_map()) { + my ($a_resource) = + $navmap->retrieveResources($passed_resource, + sub {$_[0]->is_problem() || $_[0]->is_tool() },0,1); + $symb = $a_resource->symb(); + } + } else { + unless ($mgr eq 'F') { + return (0,'error: Unable to determine current status'); + } } - # get parameter string, check for existance, rebuild string with the slot + # get parameter string, check for existence, rebuild string with the slot my $student = &Apache::lonnet::EXT("resource.0.availablestudent", $symb,$udom,$uname); my @slots = split(/:/,$student); @@ -523,13 +826,39 @@ sub release_reservation { } my $new_param = join(':',@new_slots); + # Get value of useslots parameter in effect for this user. + # If value is map or map_map, then the parm level is 2 (i.e., + # non-recursive enclosing map/folder level for specific user) + # and the symb for this reservation in slot_reservations.db + # will be the symb of the map itself. + + my $use_slots = &Apache::lonnet::EXT("resource.0.useslots", + $symb,$udom,$uname); + &Apache::lonxml::debug("use_slots is $use_slots<br />"); + + if (&Apache::lonnet::error($use_slots)) { + return (0,'error: Unable to determine current status'); + } + + my $parm_level = 1; + my $parm_symb = $passed_resource->symb(); + if ($use_slots eq 'map' || $use_slots eq 'map_map') { + $parm_level = 2; + unless ($passed_resource->is_map()) { + my ($map) = &Apache::lonnet::decode_symb($parm_symb); + $parm_symb = &Apache::lonnet::symbread($map); + } + } + my ($cnum,$cdom)=&get_course(); - # get slot reservations, check if user has one, if so remove reservation + # get slot reservations, check if user has one for the + # correct symb, and if so, remove the reservation my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); foreach my $entry (keys(%consumed)) { - if ( $consumed{$entry}->{'name'} eq ($uname.':'.$udom) ) { + if (($consumed{$entry}->{'name'} eq $uname.':'.$udom) && + ($consumed{$entry}->{'symb'} eq $parm_symb)) { &Apache::lonnet::del('slot_reservations',[$entry], $cdom,$cnum); my %storehash = ( @@ -538,25 +867,13 @@ sub release_reservation { action => 'release', context => $env{'form.context'}, ); - &Apache::lonnet::write_log('slotreservationslog',\%storehash, - 1,$uname,$udom,$cnum,$cdom); - &Apache::lonnet::write_log($cdom.'_'.$cnum.'_slotlog',\%storehash, - 1,$uname,$udom,$uname,$udom); + &Apache::lonnet::write_log('course','slotreservationslog', + \%storehash,1,$uname,$udom,$cnum,$cdom); + &Apache::lonnet::write_log('course',$cdom.'_'.$cnum.'_slotlog', + \%storehash,1,$uname,$udom,$uname,$udom); } } - my $use_slots = &Apache::lonnet::EXT("resource.0.useslots", - $symb,$udom,$uname); - &Apache::lonxml::debug("use_slots is $use_slots<br />"); - - if (&Apache::lonnet::error($use_slots)) { - return (0,'error: Unable to determine current status'); - } - - my $parm_level = 1; - if ($use_slots eq 'map' || $use_slots eq 'map_map') { - $parm_level = 2; - } # store new parameter string my $result=&Apache::lonparmset::storeparm_by_symb($symb, '0_availablestudent', @@ -635,9 +952,15 @@ sub get_slot { if ($slot_name && $slot_name ne $conflictable_slot) { my %slot=&Apache::lonnet::get_slot($slot_name); my $description1=&get_description($slot_name,\%slot); + my $slottype1=$slot{'type'}; %slot=&Apache::lonnet::get_slot($env{'form.slotname'}); my $description2=&get_description($env{'form.slotname'},\%slot); - if ($slot_name ne $env{'form.slotname'}) { + if ($slottype1 eq 'preassigned') { + $r->print('<p>'.&mt('You already have a reservation: "[_1]", assigned by your instructor.', + $description1).'</p>'. + '<p>'.&mt('Your instructor must unassign it before you can make a new reservation.'). + '</p>'); + } elsif ($slot_name ne $env{'form.slotname'}) { $r->print(<<STUFF); <form method="post" action="/adm/slotrequest"> <input type="hidden" name="symb" value="$env{'form.symb'}" /> @@ -696,7 +1019,7 @@ STUFF } my %lt = &Apache::lonlocal::texthash( - 'request' => 'Availibility list', + 'request' => 'Availability list', 'try' => 'Try again?', 'or' => 'or', ); @@ -735,7 +1058,7 @@ STUFF } sub allowed_slot { - my ($slot_name,$slot,$symb,$slots,$consumed_uniqueperiods)=@_; + my ($slot_name,$slot,$symb,$slots,$consumed_uniqueperiods,$toskip)=@_; #already started if ($slot->{'starttime'} < time) { @@ -809,9 +1132,33 @@ sub allowed_slot { return 0 if (!$userallowed); # not allowed for this resource - if (defined($slot->{'symb'}) - && $slot->{'symb'} ne $symb) { - return 0; + if (defined($slot->{'symb'})) { + my $exclude = 1; + my @symbs; + if ($slot->{'symb'} =~ /,/) { + @symbs = split(/\s*,\s*/,$slot->{'symb'}); + } else { + @symbs = ($slot->{'symb'}); + } + my ($map,$id,$url) = &Apache::lonnet::decode_symb($symb); + foreach my $reqsymb (@symbs) { + next if ($reqsymb eq ''); + my ($slotmap,$slotid,$sloturl) = &Apache::lonnet::decode_symb($reqsymb); + if ($sloturl=~/\.(page|sequence)$/) { + if (($map ne '') && ($map eq $sloturl)) { + $exclude = 0; + last; + } + } elsif ($reqsymb eq $symb) { + $exclude = 0; + last; + } + } + if ($exclude) { + unless ((ref($toskip) eq 'HASH') && ($toskip->{'symb'})) { + return 0; + } + } } my $conflict = &check_for_conflict($symb,$slot_name,$slot,$slots, @@ -839,77 +1186,119 @@ sub get_description { } sub show_choices { - my ($r,$symb,$formname)=@_; - - my ($cnum,$cdom)=&get_course(); - my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom); - my $consumed_uniqueperiods = &get_consumed_uniqueperiods(\%slots); - if (ref($consumed_uniqueperiods) eq 'HASH') { - if (&Apache::lonnet::error(%$consumed_uniqueperiods)) { - $r->print('<span class="LC_error">'. - &mt('An error occurred determining slot availability'). - '</span>'); - return; - } - } elsif ($consumed_uniqueperiods =~ /^error: /) { - $r->print('<span class="LC_error">'. - &mt('An error occurred determining slot availability'). - '</span>'); - return; - } - my (@available,$output); + my ($symb,$formname,$num,$class,$slots,$consumed_uniqueperiods,$available,$got_slots)=@_; + my $output; &Apache::lonxml::debug("Checking Slots"); - my @got_slots=&check_for_reservation($symb,'allslots'); - if ($got_slots[0] =~ /^error: /) { - $r->print('<span class="LC_error">'. - &mt('An error occurred determining slot availability'). - '</span>'); + if (!ref($available) eq 'ARRAY') { return; } - foreach my $slot (sort - { return $slots{$a}->{'starttime'} <=> $slots{$b}->{'starttime'} } - (keys(%slots))) { - - &Apache::lonxml::debug("Checking Slot $slot"); - next if (!&allowed_slot($slot,$slots{$slot},$symb,\%slots, - $consumed_uniqueperiods)); - - push(@available,$slot); - } - if (!@available) { - $output = '<div class="LC_info">'.&mt('No available times.'); + if (!@{$available}) { + $output = '<span class="LC_info">'.&mt('No available times.').'</span>'; if ($env{'form.command'} ne 'manageresv') { $output .= ' <a href="/adm/flip?postdata=return:">'. &mt('Return to last resource').'</a>'; } - $output .= '</div>'; - $r->print($output); - return; + if ($class) { + return '<div class="'.$class.'">'.$output.'</div>'; + } else { + return $output; + } + } + if (@{$available} > 1) { + my $numavailable = scalar(@{$available}); + my $numreserved = 0; + my $js; + my $j = 0; + foreach my $got (@{$got_slots}) { + unless (($got eq '') || (!defined($got))) { + $numreserved ++; + if ($env{'form.command'} eq 'manageresv') { + $js .= " currslot[$j]='$got';\n"; + $j++; + } + } + } + my $showfilter = 'none'; + $output .= '<fieldset><legend>'.&mt('Actions').'</legend>'."\n". + '<form method="post" name="reservationdisplay_'.$num. + '" action="" onsubmit="toggleSlotDisplay(this.form,'."'$num'".');">'; + my @options = ('all','filter'); + if ($numreserved) { + unshift(@options,'show'); + } + my %resmenu = &Apache::lonlocal::texthash ( + show => 'Show current reservation', + all => 'Show all', + filter => 'Search by date', + ); + foreach my $option (@options) { + my $onclick = "toggleSlotDisplay(this.form,'$num');"; + if (($option eq 'show') && ($env{'form.command'} eq 'manageresv')) { + $onclick .= "currSlotDisplay$num(this.form,'$num');"; + } + $output .= '<span class="LC_nobreak"><label>'. + '<input type="radio" class="LC_slotpick_radio" name="slotpick" value="'. + $option.'" onclick="'.$onclick.'" />'. + $resmenu{$option}. + '</label></span>'.(' ' x3)."\n"; + } + $output .= '</form>'; + my $chooserform = 'reservationchooser_'.$num; + my $starttime = $slots->{$available->[0]}->{'starttime'}; + my $endtime = $slots->{$available->[-1]}->{'starttime'}; + if ($env{'form.command'} eq 'manageresv') { + $output .= <<"ENDSCRIPT"; + +<script type="text/javascript"> +// <![CDATA[ +function currSlotDisplay$num() { + var currslot = new Array($numreserved); +$js + for (var j=0; j<$numreserved; j++) { + if (document.getElementById('LC_slotrow_$num\_'+currslot[j])) { + document.getElementById('LC_slotrow_$num\_'+currslot[j]).style.display = ''; + } + } +} +// ]]> +</script> + +ENDSCRIPT + } + $output .= + '<div id="LC_slotfilter_'.$num.'" style="display:'.$showfilter.'">'. + '<form method="post" name="'.$chooserform.'" action="">'. + '<table><tr><td>'.&mt('Open after').'</td><td>'. + &Apache::lonhtmlcommon::date_setter($chooserform,'start',$starttime,'','','','','','','',1,1). + '</td></tr><tr><td>'.&mt('Closed before').'</td><td>'. + &Apache::lonhtmlcommon::date_setter($chooserform,'end',$endtime,'','','','','','','',1,1). + '</td></tr></table><br />'. + '<input type="button" name="slotfilter" value="Search for reservable slots" onclick="updateSlotDisplay(this.form,'."'$num'".');" />'. + '</form></div><div id="LC_slotsearch_'.$num.'" style="display:none"><hr />'; } if ($env{'form.command'} eq 'manageresv') { - $output = '<table border="0">'; + $output .= '<table border="0">'; } else { - $output = &Apache::loncommon::start_data_table(); + $output .= &Apache::loncommon::start_data_table(); } - foreach my $slot (@available) { - my $description=&get_description($slot,$slots{$slot}); + foreach my $slot (@{$available}) { + my $description=&get_description($slot,$slots->{$slot}); my $form; - if ((grep(/^\Q$slot\E$/,@got_slots)) || - &space_available($slot,$slots{$slot},$symb)) { + if ((grep(/^\Q$slot\E$/,@{$got_slots})) || + &space_available($slot,$slots->{$slot},$symb)) { my $text=&mt('Select'); my $command='get'; - if (grep(/^\Q$slot\E$/,@got_slots)) { + if (grep(/^\Q$slot\E$/,@{$got_slots})) { $text=&mt('Drop Reservation'); $command='release'; } else { - my $conflict = &check_for_conflict($symb,$slot,$slots{$slot}, - \%slots, - $consumed_uniqueperiods); + my $conflict = &check_for_conflict($symb,$slot,$slots->{$slot}, + $slots,$consumed_uniqueperiods); if ($conflict) { if ($conflict =~ /^error: /) { $form = '<span class="LC_error">'. - &mt('Slot: [_1] has unknown status.',$description). - '</span>'; + &mt('Slot: [_1] has unknown status.',$description). + '</span>'; } else { $text=&mt('Change Reservation'); $command='get'; @@ -918,15 +1307,16 @@ sub show_choices { } my $escsymb=&escape($symb); if (!$form) { + my $name; if ($formname) { - $formname = 'name="'.$formname.'" '; + $name = 'name="'.$formname.'"'; } my $context = 'user'; if ($env{'form.command'} eq 'manageresv') { $context = 'usermanage'; } $form=<<STUFF; - <form method="post" action="/adm/slotrequest" $formname> + <form method="post" action="/adm/slotrequest" $name> <input type="submit" name="Select" value="$text" /> <input type="hidden" name="symb" value="$escsymb" /> <input type="hidden" name="slotname" value="$slot" /> @@ -939,9 +1329,9 @@ STUFF $form = &mt('Unavailable'); } if ($env{'form.command'} eq 'manageresv') { - $output .= '<tr>'; + $output .= '<tr id="LC_slotrow_'.$num.'_'.$slot.'" >'; } else { - $output .= &Apache::loncommon::start_data_table_row(); + $output .= &Apache::loncommon::start_data_table_row('','LC_slotrow_'.$num.'_'.$slot); } $output .= " <td>$form</td> @@ -955,9 +1345,16 @@ STUFF if ($env{'form.command'} eq 'manageresv') { $output .= '</table>'; } else { - $output .= &Apache::loncommon::end_data_table(); + $output .= &Apache::loncommon::end_data_table(); + } + if (@{$available} > 1) { + $output .= '</div></fieldset>'; + } + if ($class) { + return '<div class="'.$class.'">'.$output.'</div>'; + } else { + return $output; } - $r->print($output); } sub to_show { @@ -1112,12 +1509,13 @@ sub show_table { 'secret' => 'Secret Word', 'space' => '# of students/max', 'ip' => 'IP or DNS restrictions', - 'symb' => 'Resource slot is restricted to.', + 'symb' => 'Resource(s)/Map(s) slot is restricted to.', 'allowedsections' => 'Sections slot is restricted to.', 'allowedusers' => 'Users slot is restricted to.', 'uniqueperiod' => 'Period of time slot is unique', 'scheduled' => 'Scheduled Students', - 'proctor' => 'List of proctors'); + 'proctor' => 'List of proctors', + 'iptied' => 'Unique IP each student',); if ($crstype eq 'Community') { $show_fields{'startreserve'} = &mt('Time members can start reserving'); $show_fields{'endreserve'} = &mt('Time members can no longer reserve'); @@ -1125,7 +1523,7 @@ sub show_table { } my @show_order=('name','description','type','starttime','endtime', 'startreserve','endreserve','reservationmsg','secret','space', - 'ip','symb','allowedsections','allowedusers','uniqueperiod', + 'ip','iptied','symb','allowedsections','allowedusers','uniqueperiod', 'scheduled','proctor'); my @show = (exists($env{'form.show'})) ? &Apache::loncommon::get_env_multiple('form.show') @@ -1211,11 +1609,11 @@ sub show_table { <td valign="top"> <table> <tr> - <td rowspan="2">Deleted slots:</td> - <td><label>'.$show_radio.'Show</label></td> + <td rowspan="2">'.&mt('Deleted slots:').'</td> + <td><label>'.$show_radio.&mt('Show').'</label></td> </tr> <tr> - <td><label>'.$hide_radio.'Hide</label></td> + <td><label>'.$hide_radio.&mt('Hide').'</label></td> </tr> </table> </td> @@ -1224,15 +1622,16 @@ sub show_table { $r->print('</div>'); $r->print('<p><input type="submit" name="start" value="'.&mt('Update Display').'" /></p>'); my $linkstart='<a href="/adm/slotrequest?command=showslots&order='; - $r->print(&Apache::loncommon::start_data_table(). - &Apache::loncommon::start_data_table_header_row().' - <th></th>'); + my $tableheader = &Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row().' + <th></th>'; foreach my $which (@show_order) { if ($which ne 'proctor' && exists($show{$which})) { - $r->print('<th>'.$linkstart.$which.'">'.$show_fields{$which}.'</a></th>'); + $tableheader .= '<th>'.$linkstart.$which.'">'.$show_fields{$which}.'</a></th>'; } } - $r->print(&Apache::loncommon::end_data_table_header_row()); + $tableheader .= &Apache::loncommon::end_data_table_header_row(); + my $shownheader = 0; my %name_cache; my $slotsort = sub { @@ -1327,13 +1726,21 @@ sub show_table { localtime($slots{$slot}{'uniqueperiod'}[1]); } - my $title; + my @titles; if (exists($slots{$slot}{'symb'})) { - my (undef,undef,$res)= - &Apache::lonnet::decode_symb($slots{$slot}{'symb'}); - $res = &Apache::lonnet::clutter($res); - $title = &Apache::lonnet::gettitle($slots{$slot}{'symb'}); - $title='<a href="'.$res.'?symb='.$slots{$slot}{'symb'}.'">'.$title.'</a>'; + my @symbs; + if ($slots{$slot}{'symb'} =~ /,/) { + @symbs = split(/\s*,\s*/,$slots{$slot}{'symb'}); + } else { + @symbs = ($slots{$slot}{'symb'}); + } + foreach my $reqsymb (@symbs) { + my (undef,undef,$res) = + &Apache::lonnet::decode_symb($reqsymb); + $res = &Apache::lonnet::clutter($res); + my $title = &Apache::lonnet::gettitle($reqsymb); + push(@titles,'<a href="'.$res.'?symb='.$reqsymb.'">'.$title.'</a>'); + } } my $allowedsections; @@ -1381,30 +1788,39 @@ sub show_table { delete => 'Delete', slotlog => 'History', ); - my $edit=(<<"EDITLINK"); + my ($edit,$delete,$showlog,$remove_all); + if ($mgr) { + $edit=(<<"EDITLINK"); <a href="/adm/helper/newslot.helper?name=$slot">$lt{'edit'}</a> EDITLINK - my $delete=(<<"DELETELINK"); + $delete=(<<"DELETELINK"); <a href="/adm/slotrequest?command=delete&slotname=$slot">$lt{'delete'}</a> DELETELINK - my $showlog=(<<"LOGLINK"); + $remove_all=&remove_link($slot,'remove all').'<br />'; + + if ($ids eq '') { + undef($remove_all); + } else { + undef($delete); + } + } + + $showlog=(<<"LOGLINK"); <a href="/adm/slotrequest?command=slotlog&slotname=$slot">$lt{'slotlog'}</a> LOGLINK - my $remove_all=&remove_link($slot,'remove all').'<br />'; - - if ($ids eq '') { - undef($remove_all); - } else { - undef($delete); - } if ($slots{$slot}{'type'} ne 'schedulable_student') { undef($showlog); undef($remove_all); } + unless ($shownheader) { + $r->print($tableheader); + $shownheader = 1; + } + my $row_start=&Apache::loncommon::start_data_table_row(); my $row_end=&Apache::loncommon::end_data_table_row(); $r->print($row_start. @@ -1449,8 +1865,18 @@ LOGLINK if (exists($show{'ip'})) { $colspan++;$r->print("<td>$slots{$slot}{'ip'}</td>\n"); } + if (exists($show{'iptied'})) { + $colspan++; + if ($slots{$slot}{'iptied'} eq 'yes') { + $r->print('<td>'.&mt('Yes')."</td>\n"); + } elsif ($slots{$slot}{'iptied'} eq 'answer') { + $r->print('<td>'.&mt('Yes, including post-answer date')."</td>\n"); + } else { + $r->print('<td>'.&mt('No')."</td>\n"); + } + } if (exists($show{'symb'})) { - $colspan++;$r->print("<td>$title</td>\n"); + $colspan++;$r->print("<td>".join('<br />',@titles)."</td>\n"); } if (exists($show{'allowedsections'})) { $colspan++;$r->print("<td>$allowedsections</td>\n"); @@ -1473,12 +1899,18 @@ $row_end STUFF } } - $r->print(&Apache::loncommon::end_data_table().'</form>'); + if ($shownheader) { + $r->print(&Apache::loncommon::end_data_table()); + } else { + $r->print('<p>'.&mt('No slots meet the criteria for display').'</p>'); + } + $r->print('</form>'); return; } sub manage_reservations { - my ($r,$crstype) = @_; + my ($r,$crstype,$slots,$consumed_uniqueperiods,$allavailable) = @_; + my ($cnum,$cdom)=&get_course(); my $navmap = Apache::lonnavmaps::navmap->new(); $r->print('<p>' .&mt('Instructors may use a reservation system to place restrictions on when and where assignments can be worked on.') @@ -1497,156 +1929,246 @@ sub manage_reservations { &Apache::lonnet::logthis('Manage Reservations - could not create navmap object in '.lc($crstype).':'.$env{'request.course.id'}); return; } - my (%parent,%shownparent,%container,%container_title,%contents); - my ($depth,$count,$reservable,$lastcontainer,$rownum) = (0,0,0,0,0); - my @backgrounds = ("LC_odd_row","LC_even_row"); + if (ref($consumed_uniqueperiods) eq 'HASH') { + if (&Apache::lonnet::error(%$consumed_uniqueperiods)) { + $r->print('<span class="LC_error">'. + &mt('An error occurred determining slot availability.'). + '</span>'); + return; + } + } elsif ($consumed_uniqueperiods =~ /^error: /) { + $r->print('<span class="LC_error">'. + &mt('An error occurred determining slot availability.'). + '</span>'); + return; + } + my (%output,%slotinfo,%statusbymap,%repsymbs,%shownmaps); + my @possibles = $navmap->retrieveResources(undef, + sub { $_[0]->is_problem() || $_[0]->is_tool() },1,0); + + foreach my $resource (@possibles) { + my ($useslots) = $resource->slot_control(); + next if (($useslots eq '') || ($useslots =~ /^\s*no\s*$/i)); + my $symb = $resource->symb(); + my ($slot_status,$date,$slot_name) = $resource->check_for_slot('0'); + my ($msg,$get_choices,$slotdescription); + my $status = $resource->simpleStatus('0'); + my ($msg,$get_choices,$slotdescription); + if ($slot_name ne '') { + my %slot=&Apache::lonnet::get_slot($slot_name); + $slotdescription=&get_description($slot_name,\%slot); + } + if ($slot_status == $resource->NOT_IN_A_SLOT) { + $msg=&mt('No current reservation.'); + $get_choices = 1; + } elsif ($slot_status == $resource->NEEDS_CHECKIN) { + $msg='<span class="LC_nobreak">'.&mt('Reserved:'). + ' '.$slotdescription.'</span><br />'. + &mt('Access requires proctor validation.'); + } elsif ($slot_status == $resource->WAITING_FOR_GRADE) { + $msg=&mt('Submitted and currently in grading queue.'); + } elsif ($slot_status == $resource->CORRECT) { + $msg=&mt('Problem is unavailable.'); + } elsif ($slot_status == $resource->RESERVED) { + $msg='<span class="LC_nobreak">'.&mt('Reserved:'). + ' '.$slotdescription.'</span><br />'. + &mt('Problem is currently available.'); + } elsif ($slot_status == $resource->RESERVED_LOCATION) { + $msg='<span class="LC_nobreak">'.&mt('Reserved:'). + ' '.$slotdescription.'</span><br />'. + &mt('Problem is available at a different location.'); + $get_choices = 1; + } elsif ($slot_status == $resource->RESERVED_LATER) { + $msg='<span class="LC_nobreak">'.&mt('Reserved:'). + ' '.$slotdescription.'</span><br />'. + &mt('Problem will be available later.'); + $get_choices = 1; + } elsif ($slot_status == $resource->RESERVABLE) { + $msg=&mt('Reservation needed'); + $get_choices = 1; + } elsif ($slot_status == $resource->RESERVABLE_LATER) { + $msg=&mt('Reservation needed: will be reservable later.'); + } elsif ($slot_status == $resource->NOTRESERVABLE) { + $msg=&mt('Reservation needed: none available.'); + } elsif ($slot_status == $resource->UNKNOWN) { + $msg=&mt('Unable to determine status due to network problems.'); + } else { + if ($status != $resource->OPEN) { + $msg = &Apache::lonnavmaps::getDescription($resource,'0'); + } + } + $output{$symb}{'msg'} = $msg; + if (($status == $resource->OPEN) && ($get_choices)) { + $output{$symb}{'hasaction'} = 1; + } + my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb); + $mapurl = &Apache::lonnet::clutter($mapurl); + unless ($mapurl =~ /default\.sequence$/) { + $shownmaps{$mapurl} = 1; + my $map = $navmap->getResourceByUrl($mapurl); + if (ref($map)) { + my @pcs = split(/,/,$map->map_hierarchy()); + shift(@pcs); + shift(@pcs); + if (@pcs) { + map { $shownmaps{$navmap->getByMapPc($_)->src()} = 1; } reverse(@pcs); + } + } + } + if (($useslots eq 'map_map') || ($useslots eq 'map')) { + if ($slot_status ne '') { + if (ref($statusbymap{$mapurl}{$slot_status}) eq 'ARRAY') { + push(@{$statusbymap{$mapurl}{$slot_status}},$symb); + } else { + $statusbymap{$mapurl}{$slot_status} = [$symb]; + } + } + } + } + + foreach my $mapurl (keys(%statusbymap)) { + if (ref($statusbymap{$mapurl}) eq 'HASH') { + if (keys(%{$statusbymap{$mapurl}}) == 1) { + my @values = values(%{$statusbymap{$mapurl}}); + my $repsymb = $values[0][0]; + if (ref($output{$repsymb}) eq 'HASH') { + $output{$mapurl}{'msg'} = $output{$repsymb}{'msg'}; + $output{$mapurl}{'hasaction'} = $output{$repsymb}{'hasaction'}; + } + $repsymbs{$mapurl} = $repsymb; + } + } + } + + my (%parent,%container,%container_title); + my ($depth,$count,$reservable,$currcontainer,$rownum,$mapnum,$shown) = (0,0,0,0,0,0,0); + my @backgrounds = ("LC_even_row","LC_odd_row"); my $numcolors = scalar(@backgrounds); my $location=&Apache::loncommon::lonhttpdurl("/adm/lonIcons/whitespace_21.gif"); my $slotheader = '<p>'. &mt('Your reservation status for any such assignments is listed below:'). '</p>'. - '<table class="LC_data_table LC_tableOfContent">'."\n"; + '<table class="LC_data_table LC_tableOfContent" id="LC_slot_reservations">'."\n"; my $shownheader = 0; + my $currmap; my $it=$navmap->getIterator(undef,undef,undef,1,undef,undef); while (my $resource = $it->next()) { if ($resource == $it->BEGIN_MAP()) { $depth++; - $parent{$depth} = $lastcontainer; - } - if ($resource == $it->END_MAP()) { - $depth--; - $lastcontainer = $parent{$depth}; - } - if (ref($resource)) { - my $symb = $resource->symb(); - my $ressymb = $symb; - $contents{$lastcontainer} ++; - next if (!$resource->is_problem() && !$resource->is_sequence() && - !$resource->is_page()); - $count ++; - if (($resource->is_sequence()) || ($resource->is_page())) { - $lastcontainer = $count; - $container{$lastcontainer} = $resource; - $container_title{$lastcontainer} = $resource->compTitle(); - } - if ($resource->is_problem()) { - my ($useslots) = $resource->slot_control(); - next if (($useslots eq '') || ($useslots =~ /^\s*no\s*$/i)); - my ($msg,$get_choices,$slotdescription); - my $title = $resource->compTitle(); - my $status = $resource->simpleStatus('0'); - my ($slot_status,$date,$slot_name) = $resource->check_for_slot('0'); - if ($slot_name ne '') { - my %slot=&Apache::lonnet::get_slot($slot_name); - $slotdescription=&get_description($slot_name,\%slot); - } - if ($slot_status == $resource->NOT_IN_A_SLOT) { - $msg=&mt('No current reservation.'); - $get_choices = 1; - } elsif ($slot_status == $resource->NEEDS_CHECKIN) { - $msg='<span class="LC_nobreak">'.&mt('Reserved:'). - ' '.$slotdescription.'</span><br />'. - &mt('Access requires proctor validation.'); - } elsif ($slot_status == $resource->WAITING_FOR_GRADE) { - $msg=&mt('Submitted and currently in grading queue.'); - } elsif ($slot_status == $resource->CORRECT) { - $msg=&mt('Problem is unavailable.'); - } elsif ($slot_status == $resource->RESERVED) { - $msg='<span class="LC_nobreak">'.&mt('Reserved:'). - ' '.$slotdescription.'</span><br />'. - &mt('Problem is currently available.'); - } elsif ($slot_status == $resource->RESERVED_LOCATION) { - $msg='<span class="LC_nobreak">'.&mt('Reserved:'). - ' '.$slotdescription.'</span><br />'. - &mt('Problem is available at a different location.'); - $get_choices = 1; - } elsif ($slot_status == $resource->RESERVED_LATER) { - $msg='<span class="LC_nobreak">'.&mt('Reserved:'). - ' '.$slotdescription.'</span><br />'. - &mt('Problem will be available later.'); - $get_choices = 1; - } elsif ($slot_status == $resource->RESERVABLE) { - $msg=&mt('Reservation needed'); - $get_choices = 1; - } elsif ($slot_status == $resource->RESERVABLE_LATER) { - $msg=&mt('Reservation needed: will be reservable later.'); - } elsif ($slot_status == $resource->NOTRESERVABLE) { - $msg=&mt('Reservation needed: none available.'); - } elsif ($slot_status == $resource->UNKNOWN) { - $msg=&mt('Unable to determine status due to network problems.'); - } else { - if ($status != $resource->OPEN) { - $msg = &Apache::lonnavmaps::getDescription($resource,'0'); - } + $parent{$depth} = $currcontainer; + if (ref($container{$currcontainer})) { + my $currmapres = $container{$currcontainer}; + my $currmaptitle = $container_title{$currcontainer}; + $currmap = $currmapres->src(); + my $currmaptype = 'sequence'; + if ($currmapres->is_page()) { + $currmaptype = 'page'; } - $reservable ++; - my $treelevel = $depth; - my $higherup = $lastcontainer; - if ($depth > 1) { - my @maprows; - while ($treelevel > 1) { - if (ref($container{$higherup})) { - my $res = $container{$higherup}; - last if (defined($shownparent{$higherup})); - my $maptitle = $res->compTitle(); - my $type = 'sequence'; - if ($res->is_page()) { - $type = 'page'; - } - &show_map_row($treelevel,$location,$type,$maptitle, - \@maprows); - $shownparent{$higherup} = 1; - } - $treelevel --; - $higherup = $parent{$treelevel}; + if ($shownmaps{$currmap}) { + $mapnum ++; + $rownum ++; + $shown ++; + if (!$shownheader) { + $r->print($slotheader); + $shownheader = 1; } - foreach my $item (@maprows) { - $rownum ++; - my $bgcolor = $backgrounds[$rownum % $numcolors]; - if (!$shownheader) { - $r->print($slotheader); - $shownheader = 1; + my $bgcolor = $backgrounds[$shown % $numcolors]; + my ($spacers,$icon); + my $row = '<tr class="'.$bgcolor.'" id="LC_slotmaprow_'.$rownum.'">'; + if (ref($statusbymap{$currmap}) eq 'HASH') { + my ($spacers,$icon) = &show_map_row($depth-1,$location,$currmaptype,$currmaptitle); + my $arrowstate = 'open'; + if (keys(%{$statusbymap{$currmap}}) == 1) { + $arrowstate = 'closed'; + } + $row .= '<td>'.$spacers.'<img src="/adm/lonIcons/arrow.'.$arrowstate.'.gif" '. + 'id="arrow'.$mapnum.'" '.'alt="arrow" onmouseover="this.style.cursor=\'pointer\'" '. + 'onclick="'."toggleSlotMap('$mapnum','$rownum');".'" />'. + $icon.(' ' x6).'</td>'."\n"; + if (ref($output{$currmap}) eq 'HASH') { + my $formnum = $mapnum.'_'.$reservable+1; + my $class = 'LC_slotmaptext_'.$mapnum; + if ($output{$currmap}{'hasaction'}) { + $row .= '<td valign="top"><span class="'.$class.'">'. + $output{$currmap}{'msg'}. + '</span></td><td valign="top">'. + &slot_chooser($repsymbs{$currmap},$class,$formnum, + $allavailable,$slots,$consumed_uniqueperiods). + '</td>'; + } else { + $row .= '<td colspan="2" valign="middle"><span class="'.$class.'">'. + $output{$currmap}{'msg'}. + '</span></td>'; + } + $row .= '</tr>'."\n"; + } else { + $row .= '<td colspan="2"> </td></tr>'."\n"; } - $r->print('<tr class="'.$bgcolor.'">'.$item.'</tr>'."\n"); + } else { + my ($spacers,$icon) = &show_map_row($depth,$location,$currmaptype,$currmaptitle); + $row .= '<td>'.$spacers.$icon.(' ' x6).'</td><td colspan="2"> </td></tr>'."\n"; } + $r->print($row); } + } + } elsif ($resource == $it->END_MAP()) { + $depth--; + $currcontainer = $parent{$depth}; + } elsif (ref($resource)) { + my $symb = $resource->symb(); + next if (!$resource->is_problem() && !$resource->is_tool() && + !$resource->is_sequence() && !$resource->is_page()); + $count ++; + if (($resource->is_sequence()) || ($resource->is_page())) { + $currcontainer = $count; + $container{$currcontainer} = $resource; + $container_title{$currcontainer} = $resource->compTitle(); + } + if (($resource->is_problem() || $resource->is_tool)) { + next unless (exists($output{$symb})); + $reservable ++; $rownum ++; - my $bgcolor = $backgrounds[$rownum % $numcolors]; if (!$shownheader) { $r->print($slotheader); $shownheader = 1; } - $r->print('<tr class="'.$bgcolor.'"><td>'."\n"); + my $style; + if (exists($output{$currmap})) { + $style = 'none'; + } else { + $style = 'table-row'; + $shown ++; + } + my $title = $resource->compTitle(); + my $bgcolor = $backgrounds[$shown % $numcolors]; + $r->print('<tr class="'.$bgcolor.' LC_slotresrow_'.$mapnum.'"'. + ' style="display:'.$style.'" id="LC_slotresrow_'.$rownum.'">'. + '<td>'); for (my $i=0; $i<$depth; $i++) { $r->print('<img src="'.$location.'" alt="" />'); } - my $result = '<a href="'.$resource->src().'?symb='.$symb.'">'. - '<img class="LC_contentImage" src="/adm/lonIcons/'; + $r->print('<a href="'.$resource->src().'?symb='.$symb.'">'. + '<img class="LC_contentImage" src="/adm/lonIcons/'); if ($resource->is_task()) { - $result .= 'task.gif" alt="'.&mt('Task'); + $r->print('task.gif" alt="'.&mt('Task')); + } elsif ($resource->is_tool()) { + $r->print('unknown.gif" alt="'.&mt('External Tool')); } else { - $result .= 'problem.gif" alt="'.&mt('Problem'); - } - $result .= '" /><b>'.$title.'</b></a>'.(' ' x6).'</td>'; - my $hasaction; - if ($status == $resource->OPEN) { - if ($get_choices) { - $hasaction = 1; - } + $r->print('problem.gif" alt="'.&mt('Problem')); } - if ($hasaction) { - $result .= '<td valgn="middle">'.$msg.'</td>'. - '<td valign="middle">'.(' ' x6); + $r->print('" /><b>'.$title.'</b></a>'.(' ' x6).'</td>'); + my $class = 'LC_slottext_'.$mapnum; + if ($output{$symb}{'hasaction'}) { + $r->print('<td valign="top"><span class="'.$class.'">'.$output{$symb}{'msg'}.'</span></td>'. + '<td valign="top">'. + &slot_chooser($symb,$class,$reservable,$allavailable,$slots, + $consumed_uniqueperiods).'</td>'); } else { - $result .= '<td colspan="2" valign="middle">'.$msg.'</td>'; + $r->print('<td colspan="2" valign="middle">'. + '<span class="'.$class.'">'.$output{$symb}{'msg'}.'</span>'. + '</td>'); } - $r->print($result); - if ($hasaction) { - my $formname = 'manageres_'.$reservable; - &show_choices($r,$symb,$formname); - $r->print('</td>'); - } - $r->print('</tr>'); + $r->print('</tr>'."\n"); } } } @@ -1667,19 +2189,68 @@ sub manage_reservations { } sub show_map_row { - my ($depth,$location,$type,$title,$maprows) = @_; - my $output = '<td>'; + my ($depth,$location,$type,$title) = @_; + my $spacers; for (my $i=0; $i<$depth-1; $i++) { - $output .= '<img src="'.$location.'" alt="" />'; + $spacers .= '<img src="'.$location.'" alt="" />'; } + my $icon; if ($type eq 'page') { - $output .= '<img src="/adm/lonIcons/navmap.page.open.gif" alt="" /> '."\n"; + $icon = '<img src="/adm/lonIcons/navmap.page.open.gif" alt="" /> '."\n"; } else { - $output .= '<img src="/adm/lonIcons/navmap.folder.open.gif" alt="" /> '."\n"; + $icon = '<img src="/adm/lonIcons/navmap.folder.open.gif" alt="" /> '."\n"; } - $output .= $title.'</td><td colspan="2"> </td>'."\n"; - unshift (@{$maprows},$output); - return; + $icon .= $title; + return ($spacers,$icon); +} + +sub slot_chooser { + my ($symb,$class,$formnum,$allavailable,$slots,$consumed_uniqueperiods) = @_; + my $output; + my @got_slots=&check_for_reservation($symb,'allslots'); + if ($got_slots[0] =~ /^error: /) { + $output = '<span class="'.$class.'"><span class="LC_error">'. + &mt('An error occurred determining slot availability.'). + '</span></span>'; + } else { + my $formname = 'manageres_'.$formnum; + if (ref($allavailable) eq 'ARRAY') { + my @available; + if (ref($slots) eq 'HASH') { + foreach my $slot (@{$allavailable}) { + # not allowed for this resource + if (ref($slots->{$slot}) eq 'HASH') { + if ($slots->{$slot}->{'symb'} ne '') { + my ($map,$id,$url) = &Apache::lonnet::decode_symb($symb); + my $exclude = 1; + my @reqsymbs = split(/\s*,\s*/,$slots->{$slot}->{'symb'}); + if (@reqsymbs) { + if (grep(/^\Q$symb\E$/,@reqsymbs)) { + $exclude = 0; + } else { + foreach my $reqsymb (@reqsymbs) { + my (undef,undef,$sloturl) = &Apache::lonnet::decode_symb($reqsymb); + if ($sloturl=~/\.(page|sequence)$/) { + if (($map ne '') && ($map eq $sloturl)) { + $exclude = 0; + last; + } + } + } + } + next if ($exclude); + } + } + } + push(@available,$slot); + } + } + $output .= &show_choices($symb,$formname,$formnum,$class, + $slots,$consumed_uniqueperiods, + \@available,\@got_slots); + } + } + return $output; } sub show_reservations { @@ -1691,8 +2262,7 @@ sub show_reservations { $udom = $env{'user.domain'}; } my $formname = 'slotlog'; - my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; - my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my ($cnum,$cdom)=&get_course(); my $crstype = &Apache::loncommon::course_type(); my %log=&Apache::lonnet::dump('nohist_'.$cdom.'_'.$cnum.'_slotlog',$udom,$uname); if ($env{'form.origin'} eq 'aboutme') { @@ -1793,16 +2363,19 @@ sub show_reservations { if ($showntablehdr) { $r->print(&Apache::loncommon::end_data_table().'<br />'); if (($curr{'page'} > 1) || ($more_records)) { - $r->print('<table><tr>'); + $r->print('<p>'); if ($curr{'page'} > 1) { - $r->print('<td><a href="javascript:chgPage('."'previous'".');">'.&mt('Previous [_1] changes',$curr{'show'}).'</a></td>'); + $r->print('<input type="button" onclick="javascript:chgPage('."'previous'".');" value="'. + &mt('Previous [_1] changes',$curr{'show'}).'" />'); } if ($more_records) { - $r->print('<td><a href="javascript:chgPage('."'next'".');">'.&mt('Next [_1] changes',$curr{'show'}).'</a></td>'); + $r->print('<input type="button" onclick="javascript:chgPage('."'next'".');" value="'. + &mt('Next [_1] changes',$curr{'show'}).'" />'); } - $r->print('</tr></table>'); + $r->print('</p>'); $r->print(<<"ENDSCRIPT"); <script type="text/javascript"> +// <![CDATA[ function chgPage(caller) { if (caller == 'previous') { document.$formname.page.value --; @@ -1813,6 +2386,7 @@ function chgPage(caller) { document.$formname.submit(); return; } +// ]]> </script> ENDSCRIPT } @@ -1863,8 +2437,7 @@ sub show_reservations_log { return; } my $formname = 'reservationslog'; - my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; - my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my ($cnum,$cdom)=&get_course(); my %slotlog=&Apache::lonnet::dump('nohist_slotreservationslog',$cdom,$cnum); if ((keys(%slotlog))[0]=~/^error\:/) { undef(%slotlog); } @@ -2002,14 +2575,16 @@ sub show_reservations_log { if ($showntablehdr) { $r->print(&Apache::loncommon::end_data_table().'<br />'); if (($curr{'page'} > 1) || ($more_records)) { - $r->print('<table><tr>'); + $r->print('<p>'); if ($curr{'page'} > 1) { - $r->print('<td><a href="javascript:chgPage('."'previous'".');">'.&mt('Previous [_1] changes',$curr{'show'}).'</a></td>'); + $r->print('<input type="button" onclick="javascript:chgPage('."'previous'".');" value="'. + &mt('Previous [_1] changes',$curr{'show'}).'" />'); } if ($more_records) { - $r->print('<td><a href="javascript:chgPage('."'next'".');">'.&mt('Next [_1] changes',$curr{'show'}).'</a></td>'); + $r->print('<input type="button" onclick="javascript:chgPage('."'next'".');" value="'. + &mt('Next [_1] changes',$curr{'show'}).'" />'); } - $r->print('</tr></table>'); + $r->print('</p>'); $r->print(<<"ENDSCRIPT"); <script type="text/javascript"> function chgPage(caller) { @@ -2050,7 +2625,7 @@ sub get_resource_title { $maptitle = $maptitles->{$mapurl}; } else { if ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) { - $maptitle=&mt('Main Course Documents'); + $maptitle=&mt('Main Content'); } else { $maptitle=&Apache::lonnet::gettitle($mapurl); } @@ -2149,8 +2724,9 @@ sub display_filter { } $output .= '</select></td>'; } - $output .= '<td> </td><td valign="middle"><input type="submit" value="'. - &mt('Update Display').'" /></tr></table>'. + $output .= '<td> </td></tr></table>'. + '<p><input type="submit" value="'. + &mt('Update Display').'" /></p>'. '<p class="LC_info">'. &mt('Only changes made from servers running LON-CAPA [_1] or later are displayed.' ,'2.9.0'); @@ -2298,7 +2874,7 @@ ENDPICK } sub csvupload_javascript_reverse_associate { - my $error1=&mt('You need to specify the name, starttime, endtime and a type'); + my $error1=&mt('You need to specify the name, start time, end time and a type.'); return(<<ENDPICK); function verify(vf) { var foundstart=0; @@ -2324,7 +2900,7 @@ ENDPICK } sub csvupload_javascript_forward_associate { - my $error1=&mt('You need to specify the name, starttime, endtime and a type'); + my $error1=&mt('You need to specify the name, start time, end time and a type.'); return(<<ENDPICK); function verify(vf) { var foundstart=0; @@ -2356,8 +2932,10 @@ sub csv_upload_map { if (!$env{'form.datatoken'}) { $datatoken=&Apache::loncommon::upfile_store($r); } else { - $datatoken=$env{'form.datatoken'}; - &Apache::loncommon::load_tmp_file($r); + $datatoken=&Apache::loncommon::valid_datatoken($env{'form.datatoken'}); + if ($datatoken ne '') { + &Apache::loncommon::load_tmp_file($r,$datatoken); + } } my @records=&Apache::loncommon::upfile_record_sep(); if ($env{'form.noFirstLine'}) { shift(@records); } @@ -2394,10 +2972,11 @@ sub csvupload_fields { ['endreserve','Reservation End Time'], ['reservationmsg','Message when reservation changed'], ['ip','IP or DNS restriction'], + ['iptied','Unique IP each student'], ['proctor','List of proctor ids'], ['description','Slot Description'], ['maxspace','Maximum number of reservations'], - ['symb','Resource Restriction'], + ['symb','Resource(s)/Map(s) Restriction'], ['uniqueperiod','Date range of slot exclusion'], ['secret','Secret word proctor uses to validate'], ['allowedsections','Sections slot is restricted to'], @@ -2407,7 +2986,10 @@ sub csvupload_fields { sub csv_upload_assign { my ($r,$mgr)= @_; - &Apache::loncommon::load_tmp_file($r); + my $datatoken = &Apache::loncommon::valid_datatoken($env{'form.datatoken'}); + if ($datatoken ne '') { + &Apache::loncommon::load_tmp_file($r,$datatoken); + } my @slotdata = &Apache::loncommon::upfile_record_sep(); if ($env{'form.noFirstLine'}) { shift(@slotdata); } my %fields=&Apache::grades::get_fields(); @@ -2465,8 +3047,12 @@ sub csv_upload_assign { } if ($entries{$fields{'startreserve'}}) { - $slot{'startreserve'}= - &UnixDate($entries{$fields{'startreserve'}},"%s"); + my $date = &UnixDate($entries{$fields{'startreserve'}},"%s"); + if ($date eq '') { + push(@errors,"$name -- No reservation start time set for slot -- value provided had invalid format"); + } else { + $slot{'startreserve'} = $date; + } } if (defined($slot{'startreserve'}) && $slot{'startreserve'} > $slot{'starttime'}) { @@ -2475,8 +3061,12 @@ sub csv_upload_assign { } if ($entries{$fields{'endreserve'}}) { - $slot{'endreserve'}= - &UnixDate($entries{$fields{'endreserve'}},"%s"); + my $date = &UnixDate($entries{$fields{'endreserve'}},"%s"); + if ($date eq '') { + push(@errors,"$name -- No reservation end time set for slot -- value provided had invalid format"); + } else { + $slot{'endreserve'} = $date; + } } if (defined($slot{'endreserve'}) && $slot{'endreserve'} > $slot{'starttime'}) { @@ -2504,14 +3094,51 @@ sub csv_upload_assign { $slot{$key}=$entries{$fields{$key}}; } } - + if ($entries{$fields{'iptied'}} =~ /^\s*(yes|1)\s*$/i) { + $slot{'iptied'}='yes'; + } elsif ($entries{$fields{'iptied'}} =~ /^\s*answer\s*$/i) { + $slot{'iptied'}='answer'; + } + if ($entries{$fields{'allowedusers'}}) { + $entries{$fields{'allowedusers'}} =~ s/^\s+//; + $entries{$fields{'allowedusers'}} =~ s/\s+$//; + my @allowedusers; + foreach my $poss (split(/\s*,\s*/,$entries{$fields{'allowedusers'}})) { + my ($possuname,$possudom) = split(/:/,$poss); + if (($possuname =~ /^$match_username$/) && ($possudom =~ /^$match_domain$/)) { + unless (grep(/^\Q$poss\E$/,@allowedusers)) { + push(@allowedusers,$poss); + } + } + } + if (@allowedusers > 0) { + $slot{'allowedusers'} = join(',',@allowedusers); + } + } + if ($entries{$fields{'allowedsections'}}) { + $entries{$fields{'allowedsections'}} =~ s/^\s+//; + $entries{$fields{'allowedsections'}} =~ s/\s+$//; + my @allowedsections; + foreach my $poss (split(/\s*,\s*/,$entries{$fields{'allowedsections'}})) { + if (($poss !~ /\W/) && ($poss ne 'none')) { + unless (grep(/^\Q$poss\E$/,@allowedsections)) { + push(@allowedsections,$poss); + } + } + } + if (@allowedsections > 0) { + $slot{'allowedsections'} = join(',',@allowedsections); + } + } if ($entries{$fields{'uniqueperiod'}}) { - my ($start,$end)=split(',',$entries{$fields{'uniqueperiod'}}); - my @times=(&UnixDate($start,"%s"), - &UnixDate($end,"%s")); - $slot{'uniqueperiod'}=\@times; + my ($start,$end)= map { &UnixDate($_,"%s"); } split(',',$entries{$fields{'uniqueperiod'}}); + if (($start ne '') && ($end ne '')) { + $slot{'uniqueperiod'}=[$start,$end]; + } else { + push(@errors,"$name -- Slot's unique period ignored -- one or both of the comma separated values for start and end had an invalid format"); + } } - if (defined($slot{'uniqueperiod'}) + if (ref($slot{'uniqueperiod'}) eq 'ARRAY' && $slot{'uniqueperiod'}[0] > $slot{'uniqueperiod'}[1]) { push(@errors,"$name not created -- Slot's unique period start time is later than the unique period's end time."); next; @@ -2576,6 +3203,7 @@ sub handler { my $vgr=&Apache::lonnet::allowed('vgr',$env{'request.course.id'}); my $mgr=&Apache::lonnet::allowed('mgr',$env{'request.course.id'}); + my (%slots,$consumed_uniqueperiods); if ($env{'form.command'} eq 'showslots') { if (($vgr ne 'F') && ($mgr ne 'F')) { $env{'form.command'} = 'manageresv'; @@ -2596,9 +3224,14 @@ sub handler { if (ref($brcrum) eq 'ARRAY') { push(@{$brcrum},{href=>"/adm/slotrequest?command=showresv",text=>$title}); } - } elsif ($env{'form.command'} eq 'manageresv') { - $title = 'Manage Reservations'; - $brcrum =[{href=>"/adm/slotrequest?command=manageresv",text=>$title}]; + } elsif (($env{'form.requestattempt'}) || ($env{'form.command'} eq 'manageresv')) { + if ($env{'form.command'} eq 'manageresv') { + $title = 'Manage Reservations'; + $brcrum =[{href=>"/adm/slotrequest?command=manageresv",text=>$title}]; + } + my ($cnum,$cdom)=&get_course(); + %slots = &Apache::lonnet::get_course_slots($cnum,$cdom); + $consumed_uniqueperiods = &get_consumed_uniqueperiods(\%slots); } elsif ($vgr eq 'F') { if ($env{'form.command'} =~ /^(slotlog|showslots|uploadstart|csvuploadmap|csvuploadassign|delete|release|remove_registration)$/) { $brcrum =[{href=>"/adm/slotrequest?command=showslots", @@ -2622,11 +3255,23 @@ sub handler { } else { $brcrum =[]; } - &start_page($r,$title,$brcrum); + my ($symb,$js,$available,$allavailable,$got_slots); + $available = []; + if ($env{'form.requestattempt'}) { + $symb=&unescape($env{'form.symb'}); + @{$got_slots}=&check_for_reservation($symb,'allslots'); + } + if (($env{'form.requestattempt'}) || ($env{'form.command'} eq 'manageresv')) { + $js = &reservation_js(\%slots,$consumed_uniqueperiods,$available,$got_slots,$symb); + } + &start_page($r,$title,$brcrum,$js); if ($env{'form.command'} eq 'manageresv') { + $allavailable = $available; + undef($available); + undef($got_slots); my $crstype = &Apache::loncommon::course_type(); - &manage_reservations($r,$crstype); + &manage_reservations($r,$crstype,\%slots,$consumed_uniqueperiods,$allavailable); } elsif ($env{'form.command'} eq 'showresv') { &show_reservations($r,$env{'form.uname'},$env{'form.udom'}); } elsif ($env{'form.command'} eq 'showslots' && $vgr eq 'F') { @@ -2656,7 +3301,7 @@ sub handler { } &csv_upload_map($r); } - } elsif ($env{'form.command'} eq 'slotlog' && $mgr eq 'F') { + } elsif (($env{'form.command'} eq 'slotlog') && ($vgr eq 'F')) { &show_reservations_log($r); } else { my $symb=&unescape($env{'form.symb'}); @@ -2678,12 +3323,15 @@ sub handler { my ($status) = &Apache::lonhomework::check_slot_access('0',$type); if ($status eq 'CAN_ANSWER' || $status eq 'NEEDS_CHECKIN' || - $status eq 'WAITING_FOR_GRADE') { + $status eq 'WAITING_FOR_GRADE' || + $status eq 'NEED_DIFFERENT_IP') { &fail($r,'not_allowed'); return OK; } if ($env{'form.requestattempt'}) { - &show_choices($r,$symb); + $r->print('<div class="LC_left_float">'); + $r->print(&show_choices($symb,undef,undef,undef,\%slots,$consumed_uniqueperiods,$available,$got_slots)); + $r->print('</div><div style="padding:0;clear:both;margin:0;border:0"></div>'); } elsif ($env{'form.command'} eq 'release') { &release_slot($r,$symb); } elsif ($env{'form.command'} eq 'get') {