# The LearningOnline Network with CAPA # Handler for requesting to have slots added to a students record # # $Id: slotrequest.pm,v 1.147 2023/07/12 15:48:23 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # ### package Apache::slotrequest; use strict; use Apache::Constants qw(:common :http :methods); use Apache::loncommon(); use Apache::lonlocal; use Apache::lonnet; use Apache::lonnavmaps(); use Date::Manip; use lib '/home/httpd/lib/perl/'; use LONCAPA qw(:DEFAULT :match); sub fail { my ($r,$code)=@_; if ($code eq 'not_valid') { $r->print('
'.&mt('Unable to understand what resource you wanted to sign up for.').'
'); } elsif ($code eq 'not_available') { $r->print(''.&mt('No slots are available.').'
'); } elsif ($code eq 'not_allowed') { $r->print(''.&mt('Not allowed to sign up or change reservations at this time.').'
'); } else { $r->print(''.&mt('Failed.').'
'); } &return_link($r); &end_page($r); } sub start_page { my ($r,$title,$brcrum,$bread_crumbs_component,$js,$mgr)=@_; my $args; if (ref($brcrum) eq 'ARRAY') { $args = {bread_crumbs => $brcrum}; if ($bread_crumbs_component) { $args->{bread_crumbs_component} = $bread_crumbs_component; } } 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 }; } } unless (($env{'form.context'} eq 'usermanage') || (($mgr eq 'F') && (($env{'form.command'} eq 'release') || ($env{'form.command'} eq 'remove_registration')))) { if ($env{'form.symb'}) { my $symb=&unescape($env{'form.symb'}); my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb); if ($resurl =~ /ext\.tool$/) { my $target; my ($marker,$exttool) = (split(m{/},$resurl))[3,4]; $marker=~s/\D//g; if (($marker) && ($exttool) && ($env{'request.course.id'})) { my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; my ($idx,$crstool,$is_tool,%toolhash,%toolsettings); if ($resurl eq "adm/$cdom/$cnum/$marker/$exttool") { my %toolsettings=&Apache::lonnet::dump('exttool_'.$marker,$cdom,$cnum); $target = $toolsettings{'target'}; } } if ($target eq 'iframe') { $args->{'only_body'} = 1; } } } } $r->print(&Apache::loncommon::start_page($title,$js,$args)); } sub end_page { my ($r)=@_; $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"; ENDSCRIPT } =pod slot_reservations db - keys are - slotname\0id -> value is an hashref of name -> user@domain of holder timestamp -> timestamp of reservation symb -> symb of resource that it is reserved for =cut sub get_course { (undef,my $courseid)=&Apache::lonnet::whichuser(); my $cdom=$env{'course.'.$courseid.'.domain'}; my $cnum=$env{'course.'.$courseid.'.num'}; return ($cnum,$cdom); } sub get_reservation_ids { my ($slot_name)=@_; my ($cnum,$cdom)=&get_course(); my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); if (&Apache::lonnet::error(%consumed)) { return 'error: Unable to determine current status'; } my ($tmp)=%consumed; if ($tmp=~/^error: 2 / ) { return 0; } return keys(%consumed); } sub space_available { my ($slot_name,$slot)=@_; my $max=$slot->{'maxspace'}; if (!defined($max)) { return 1; } my $consumed=scalar(&get_reservation_ids($slot_name)); if ($consumed < $max) { return 1 } return 0; } 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)); &Apache::lonxml::debug(" slot list is ".join(':',@slots)); my ($cnum,$cdom)=&get_course(); my %slots=&Apache::lonnet::get('slots', [@slots], $cdom, $cnum); if (&Apache::lonnet::error($student) || &Apache::lonnet::error($course) || &Apache::lonnet::error(%slots)) { return 'error: Unable to determine current status'; } my @got; my @sorted_slots = &Apache::loncommon::sorted_slots(\@slots,\%slots,'starttime'); foreach my $slot_name (@sorted_slots) { next if (!defined($slots{$slot_name}) || !ref($slots{$slot_name})); &Apache::lonxml::debug(time." $slot_name ". $slots{$slot_name}->{'starttime'}." -- ". $slots{$slot_name}->{'startreserve'}." -- ". $slots{$slot_name}->{'endreserve'}); if (($slots{$slot_name}->{'endtime'} > time) && ($slots{$slot_name}->{'startreserve'} < time) && ((!$slots{$slot_name}->{'endreserve'}) || ($slots{$slot_name}->{'endreserve'} > time))) { # between start of reservation time and end of reservation time # and before end of slot if ($mode eq 'allslots') { push(@got,$slot_name); } else { return($slot_name, $slots{$slot_name}); } } } if ($mode eq 'allslots' && @got) { return @got; } return (undef,undef); } sub get_consumed_uniqueperiods { my ($slots) = @_; my $navmap=Apache::lonnavmaps::navmap->new; if (!defined($navmap)) { return 'error: Unable to determine current status'; } my @problems = $navmap->retrieveResources(undef, sub { $_[0]->is_problem() || $_[0]->is_tool() },1,0); my %used_slots; foreach my $problem (@problems) { my $symb = $problem->symb(); 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'}); if (&Apache::lonnet::error($student) || &Apache::lonnet::error($course)) { return 'error: Unable to determine current status'; } foreach my $slot (split(/:/,$student), split(/:/, $course)) { $used_slots{$slot}=1; } } if (!ref($slots)) { my ($cnum,$cdom)=&get_course(); my %slots=&Apache::lonnet::get('slots', [keys(%used_slots)], $cdom, $cnum); if (&Apache::lonnet::error(%slots)) { return 'error: Unable to determine current status'; } $slots = \%slots; } my %consumed_uniqueperiods; foreach my $slot_name (keys(%used_slots)) { next if (!defined($slots->{$slot_name}) || !ref($slots->{$slot_name})); next if (!defined($slots->{$slot_name}{'uniqueperiod'}) || !ref($slots->{$slot_name}{'uniqueperiod'})); $consumed_uniqueperiods{$slot_name} = $slots->{$slot_name}{'uniqueperiod'}; } return \%consumed_uniqueperiods; } sub check_for_conflict { my ($symb,$new_slot_name,$new_slot,$slots,$consumed_uniqueperiods)=@_; if (!defined($new_slot->{'uniqueperiod'})) { return undef; } if (!ref($consumed_uniqueperiods)) { if ($consumed_uniqueperiods =~ /^error: /) { return $consumed_uniqueperiods; } else { $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}}; if (! ($start < $new_uniq_start && $end < $new_uniq_start) || ($start > $new_uniq_end && $end > $new_uniq_end )) { return $slot_name; } } return undef; } sub make_reservation { my ($slot_name,$slot,$symb,$cnum,$cdom)=@_; my $value=&Apache::lonnet::EXT("resource.0.availablestudent",$symb, $env{'user.domain'},$env{'user.name'}); &Apache::lonxml::debug("value is $value".&mt('A network error has occurred.').'
'); return; } if (!%consumed) { $r->print(''.&mt('Slot [_1] has no reservations.', ''.$slot_name.'').'
'); return; } my @names = map { $consumed{$_}{'name'} } (sort(keys(%consumed))); my $names = join(' ',@names); my $msg = &mt('Remove all of [_1] from slot [_2]?',$names,$slot_name); &remove_registration_confirmation($r,$msg,['entry','slotname','context']); } sub remove_registration_user { my ($r) = @_; my $slot_name = $env{'form.slotname'}; my $name = &Apache::loncommon::plainname($env{'form.uname'}, $env{'form.udom'}); my $title = &Apache::lonnet::gettitle($env{'form.symb'}); my $msg = &mt('Remove [_1] from slot [_2] for [_3]', $name,$slot_name,$title); &remove_registration_confirmation($r,$msg,['uname','udom','slotname', 'entry','symb','context']); } sub remove_registration_confirmation { my ($r,$msg,$inputs) =@_; my $hidden_input; foreach my $parm (@{$inputs}) { $hidden_input .= '&\'').'" />'."\n"; } my %lt = &Apache::lonlocal::texthash( 'yes' => 'Yes', 'no' => 'No', ); $r->print(<<"END_CONFIRM");$msg
END_CONFIRM } sub release_all_slot { my ($r,$mgr)=@_; my $slot_name = $env{'form.slotname'}; my ($cnum,$cdom)=&get_course(); my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); $r->print(''.&mt('Releasing reservations').'
'); foreach my $entry (sort { $consumed{$a}{'name'} cmp $consumed{$b}{'name'} } (keys(%consumed))) { my ($uname,$udom) = split(':',$consumed{$entry}{'name'}); my ($result,$msg) = &release_reservation($slot_name,$uname,$udom, $consumed{$entry}{'symb'},$mgr); if (!$result) { $r->print(''.&mt($msg).'
'); } else { $r->print("$msg
"); } $r->rflush(); } $r->print(''. &mt('Return to slot list').'
'); &return_link($r); } sub release_slot { my ($r,$symb,$slot_name,$inhibit_return_link,$mgr)=@_; if ($slot_name eq '') { $slot_name=$env{'form.slotname'}; } my ($uname,$udom) = ($env{'user.name'}, $env{'user.domain'}); if ($mgr eq 'F' && defined($env{'form.uname'}) && defined($env{'form.udom'})) { ($uname,$udom) = ($env{'form.uname'}, $env{'form.udom'}); } if ($mgr eq 'F' && defined($env{'form.symb'})) { $symb = &unescape($env{'form.symb'}); } my ($result,$msg) = &release_reservation($slot_name,$uname,$udom,$symb,$mgr); if (!$result) { $r->print(''.&mt($msg).'
'); } else { $r->print("$msg
"); } if ($mgr eq 'F') { $r->print(''. &mt('Return to slot list').'
'); } if (!$inhibit_return_link) { &return_link($r); } return $result; } sub release_reservation { my ($slot_name,$uname,$udom,$symb,$mgr) = @_; my %slot=&Apache::lonnet::get_slot($slot_name); my $description=&get_description($slot_name,\%slot); my $msg; if ($mgr ne 'F') { if ($slot{'starttime'} < time) { return (0,&mt('Not allowed to release Reservation: [_1], as it has already started.',$description)); } } my $context = $env{'form.context'}; # get navmap object my $navmap=Apache::lonnavmaps::navmap->new; if (!defined($navmap)) { return (0,'error: Unable to determine current status'); } my ($cnum,$cdom)=&get_course(); # get slot reservations, check if user has reservation my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); # # If release is because of a reservation *change*, symb(s) associated with reservation # being dropped may differ from the current symb. # # We need to get symb(s) from slot_reservations.db, and for each symb, update # the value of the availablestudent parameter, at the appropriate level # (as dictated by the value of the useslots parameter for the symb and user). # # We also delete all entries for the slot being released, for the specific user. # my $conflict; if (($env{'form.command'} eq 'change') && ($slot_name eq $env{'form.releaseslot'}) && ($env{'form.slotname'} ne $slot_name)) { my %changedto = &Apache::lonnet::get_slot($env{'form.slotname'}); # check for conflicts my ($to_uniq_start,$to_uniq_end,$from_uniq_start,$from_uniq_end); if (ref($changedto{'uniqueperiod'}) eq 'ARRAY') { ($to_uniq_start,$to_uniq_end) = @{$changedto{'uniqueperiod'}}; } if (ref($slot{'uniqueperiod'}) eq 'ARRAY') { ($from_uniq_start,$from_uniq_end) = @{$slot{'uniqueperiod'}}; } my $to_start = $changedto{'starttime'}; my $to_end = $changedto{'endtime'}; my $from_start = $slot{'starttime'}; my $from_end = $slot{'endtime'}; if (! ($from_start < $to_uniq_start && $from_end < $to_uniq_start) || ($from_start > $to_uniq_end && $from_end > $to_uniq_end )) { $conflict = 1; } if (! ($to_start < $from_uniq_start && $to_end < $from_uniq_start) || ($to_start > $from_uniq_end && $to_end > $from_uniq_end )) { $conflict = 1; } if ($conflict) { my %symbs_for_slot; my (%to_delete,%failed,%released); foreach my $entry (keys(%consumed)) { if ( $consumed{$entry}->{'name'} eq ($uname.':'.$udom) ) { $symbs_for_slot{$consumed{$entry}->{'symb'}} = 1; $to_delete{$entry} = 1; } } if (keys(%to_delete)) { my @removals = keys(%to_delete); if (&Apache::lonnet::del('slot_reservations',\@removals, $cdom,$cnum) eq 'ok') { foreach my $item (keys(%symbs_for_slot)) { my $result = &update_selectable($navmap,$slot_name,$item,$cdom, $cnum,$udom,$uname,$context); if ($result =~ /^error/) { $failed{$item} = 1; } else { $released{$item} = 1; } } } } if (keys(%released)) { $msg = ''. &mt('Released Reservation: [_1]',$description).' '. &mt('The following items had their reservation status change').':'; my (%folders,%pages,%container,%titles); foreach my $item (keys(%released)) { my $res = $navmap->getBySymb($item); if (ref($res)) { $titles{$item} = $res->title(); if ($res->is_map()) { $folders{$item}{'title'} = $titles{$item}; if ($res->is_page()) { $pages{$item}{'title'} = $titles{$item}; } else { $folders{$item}{'title'} = $titles{$item}; } } else { my $mapsrc = $res->enclosing_map_src(); my $map = $navmap->getResourceByUrl($mapsrc); if (ref($map)) { if ($map->id() eq '0.0') { $container{$mapsrc}{'title'} &mt('Top level of course'); } else { $container{$mapsrc}{'title'} = $map->title(); if ($map->is_page()) { $container{$mapsrc}{'page'} = 1; } } } $container{$mapsrc}{'resources'}{$item} = 1; } } } $msg .= ''.&mt('Slot [_1] marked as deleted.',''.$slot_name.'').'
'); } else { $r->print(''.&mt('An error occurred when attempting to delete slot: [_1]',''.$slot_name.'')." ($ret)
"); } } else { if (%consumed) { $r->print(''.&mt('Slot [_1] has active reservations.',''.$slot_name.'').'
'); } else { $r->print(''.&mt('Slot [_1] does not exist.',''.$slot_name.'').'
'); } } $r->print(''. &mt('Return to slot list').'
'); &return_link($r); } sub return_link { my ($r) = @_; my $target = &return_target(); if (($env{'form.command'} eq 'manageresv') || ($env{'form.context'} eq 'usermanage')) { $r->print(''. &mt('Return to reservations').'
'); } else { $r->print(''. &mt('Return to last resource').'
'); } } sub return_target { my ($target,$ltitarget,$deeplinktarget); if ($env{'request.lti.login'}) { $ltitarget = $env{'request.lti.target'}; } if ($env{'request.deeplink.login'}) { $deeplinktarget = $env{'request.deeplink.target'}; } if (($ltitarget eq 'iframe') || ($deeplinktarget eq '_self')) { $target = '_self'; } else { $target = '_top'; } return $target; } sub get_slot { my ($r,$symb,$conflictable_slot,$inhibit_return_link)=@_; my %slot=&Apache::lonnet::get_slot($env{'form.slotname'}); my $slot_name=&check_for_conflict($symb,$env{'form.slotname'},\%slot); if ($slot_name =~ /^error: (.*)/) { $r->print('' .&mt('An error occurred while attempting to make a reservation. ([_1])',$1) .'
'); &return_link($r); return 0; } 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 ($slottype1 eq 'preassigned') { $r->print(''.&mt('You already have a reservation: "[_1]", assigned by your instructor.', $description1).'
'. ''.&mt('Your instructor must unassign it before you can make a new reservation.'). '
'); } elsif ($slot_name ne $env{'form.slotname'}) { $r->print(<'.&mt('Reservation currently unchanged').'
'); if ($slot_name ne '') { $r->print(''.&mt('To complete the transaction you [_1]must confirm[_2] you want to [_3]process the change[_4] to [_5].'
,'','','','',''.$description2.'')
.'
'
.&mt('Or you can choose to [_1]make no change[_2] and continue[_2] with the reservation you already had: [_3].'
,'','',''.$description1.'')
.'
' .'' .(' 'x3) .'' .'
'); } $r->print(<'.&mt('Already have a reservation: [_1].',$description1).'
'); &return_link($r); } return 0; } my ($cnum,$cdom)=&get_course(); my $reserved=&make_reservation($env{'form.slotname'}, \%slot,$symb,$cnum,$cdom); my $description=&get_description($env{'form.slotname'},\%slot); if (defined($reserved)) { my $retvalue = 0; if ($slot_name =~ /^error: (.*)/) { $r->print('' .&mt('An error occurred while attempting to make a reservation. ([_1])',$1) .'
'); } elsif ($reserved > -1) { $r->print(''.&mt('Successfully signed up: [_1]',$description).'
'); $retvalue = 1; my $person = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'}); my $subject = &mt('Reservation change: [_1]',$description); my $msgbody = &mt('Successful reservation by [_1] for [_2].',$person,$description); my $msg = &slot_change_messaging($slot{'reservationmsg'},$subject,$msgbody,'reserve'); if ($msg) { $r->print($msg); } } elsif ($reserved < 0) { $r->print(''.&mt('Already reserved: [_1]',$description).'
'); } if (!$inhibit_return_link) { &return_link($r); } return 1; } my %lt = &Apache::lonlocal::texthash( 'request' => 'Availability list', 'try' => 'Try again?', 'or' => 'or', ); my $extra_input; if ($conflictable_slot) { $extra_input=''; } $r->print(''.&mt('[_1]Failed[_2] to reserve a slot for [_3].','','',$description).'
'); $r->print(<$lt{'or'}
STUFF if (!$inhibit_return_link) { $r->print(&mt('or').''); &return_link($r); } else { $r->print(''); } return 0; } sub allowed_slot { my ($slot_name,$slot,$symb,$slots,$consumed_uniqueperiods,$toskip)=@_; #already started if ($slot->{'starttime'} < time) { return 0; } &Apache::lonxml::debug("$slot_name starttime good"); #already ended if ($slot->{'endtime'} < time) { return 0; } &Apache::lonxml::debug("$slot_name endtime good"); # not allowed to pick this one if (defined($slot->{'type'}) && $slot->{'type'} ne 'schedulable_student') { return 0; } &Apache::lonxml::debug("$slot_name type good"); # reserve time not yet started if ($slot->{'startreserve'} > time) { return 0; } # reserve time ended if (($slot->{'endreserve'}) && ($slot->{'endreserve'} < time)) { return 0; } &Apache::lonxml::debug("$slot_name reserve good"); my $userallowed=0; # its for a different set of users if (defined($slot->{'allowedsections'})) { if (!defined($env{'request.role.sec'}) && grep(/^No section assigned$/, split(',',$slot->{'allowedsections'}))) { $userallowed=1; } if (defined($env{'request.role.sec'}) && grep(/^\Q$env{'request.role.sec'}\E$/, split(',',$slot->{'allowedsections'}))) { $userallowed=1; } if (defined($env{'request.course.groups'})) { my @groups = split(/:/,$env{'request.course.groups'}); my @allowed_sec = split(',',$slot->{'allowedsections'}); foreach my $group (@groups) { if (grep {$_ eq $group} (@allowed_sec)) { $userallowed=1; last; } } } } &Apache::lonxml::debug("$slot_name sections is $userallowed"); # its for a different set of users if (defined($slot->{'allowedusers'}) && grep(/^\Q$env{'user.name'}:$env{'user.domain'}\E$/, split(',',$slot->{'allowedusers'}))) { $userallowed=1; } if (!defined($slot->{'allowedusers'}) && !defined($slot->{'allowedsections'})) { $userallowed=1; } &Apache::lonxml::debug("$slot_name user is $userallowed"); return 0 if (!$userallowed); # not allowed for this resource 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, $consumed_uniqueperiods); if ($conflict =~ /^error: /) { return 0; } elsif ($conflict ne '') { if ($slots->{$conflict}{'starttime'} < time) { return 0; } } &Apache::lonxml::debug("$slot_name symb good"); return 1; } sub get_description { my ($slot_name,$slot)=@_; my $description=$slot->{'description'}; if (!defined($description)) { $description=&mt('[_1] From [_2] to [_3]',$slot_name, &Apache::lonlocal::locallocaltime($slot->{'starttime'}), &Apache::lonlocal::locallocaltime($slot->{'endtime'})); } return $description; } sub show_choices { my ($symb,$formname,$num,$class,$slots,$consumed_uniqueperiods,$available,$got_slots)=@_; my $output; &Apache::lonxml::debug("Checking Slots"); if (!ref($available) eq 'ARRAY') { return; } if (!@{$available}) { $output = ''.&mt('No available times.').''; if ($env{'form.command'} ne 'manageresv') { my $target = &return_target(); $output .= ' '. &mt('Return to last resource').''; } if ($class) { return '' .&mt('No slots have been created in this '.lc($crstype).'.') .'
' ); return; } my %Saveable_Parameters = ('show' => 'array', 'when' => 'scalar', 'order' => 'scalar', 'deleted' => 'scalar', 'name_filter_type' => 'scalar', 'name_filter_value' => 'scalar', ); &Apache::loncommon::store_course_settings('slotrequest', \%Saveable_Parameters); &Apache::loncommon::restore_course_settings('slotrequest', \%Saveable_Parameters); &Apache::grades::init_perm(); my ($classlist,$section,$fullname)=&Apache::grades::getclasslist('all'); &Apache::grades::reset_perm(); # what to display filtering my %show_fields=&Apache::lonlocal::texthash( 'name' => 'Slot Name', 'description' => 'Description', 'type' => 'Type', 'starttime' => 'Start time', 'endtime' => 'End Time', 'startreserve' => 'Time students can start reserving', 'endreserve' => 'Time students can no longer reserve', 'reservationmsg' => 'Message triggered by reservation', 'secret' => 'Secret Word', 'space' => '# of students/max', 'ip' => 'IP or DNS restrictions', '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', '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'); $show_fields{'scheduled'} = &mt('Scheduled Members'); } my @show_order=('name','description','type','starttime','endtime', 'startreserve','endreserve','reservationmsg','secret','space', 'ip','iptied','symb','allowedsections','allowedusers','uniqueperiod', 'scheduled','proctor'); my @show = (exists($env{'form.show'})) ? &Apache::loncommon::get_env_multiple('form.show') : keys(%show_fields); my %show = map { $_ => 1 } (@show); #when filtering setup my %when_fields=&Apache::lonlocal::texthash( 'now' => 'Open now', 'nextweek' => 'Open within the next week', 'lastweek' => 'Were open last week', 'willopen' => 'Will open later', 'wereopen' => 'Were open', 'any' => 'Anytime', ); my @when_order=('any','now','nextweek','lastweek','willopen','wereopen'); $when_fields{'select_form_order'} = \@when_order; my $when = (exists($env{'form.when'})) ? $env{'form.when'} : 'now'; #display of students setup my %stu_display_fields= &Apache::lonlocal::texthash('username' => 'User name', 'fullname' => 'Full name', ); my @stu_display_order=('fullname','username'); my @stu_display = (exists($env{'form.studisplay'})) ? &Apache::loncommon::get_env_multiple('form.studisplay') : keys(%stu_display_fields); my %stu_display = map { $_ => 1 } (@stu_display); #name filtering setup my %name_filter_type_fields= &Apache::lonlocal::texthash('substring' => 'Substring', 'exact' => 'Exact', #'reg' => 'Regular Expression', ); my @name_filter_type_order=('substring','exact'); $name_filter_type_fields{'select_form_order'} = \@name_filter_type_order; my $name_filter_type = (exists($env{'form.name_filter_type'})) ? $env{'form.name_filter_type'} : 'substring'; my $name_filter = {'type' => $name_filter_type, 'value' => $env{'form.name_filter_value'},}; #deleted slot filtering #default to hide if no value $env{'form.deleted'} ||= 'hide'; my $hide_radio = &Apache::lonhtmlcommon::radio('deleted',$env{'form.deleted'},'hide'); my $show_radio = &Apache::lonhtmlcommon::radio('deleted',$env{'form.deleted'},'show'); $r->print(''); return; } sub manage_reservations { my ($r,$crstype,$slots,$consumed_uniqueperiods,$allavailable) = @_; my ($cnum,$cdom)=&get_course(); my $navmap = Apache::lonnavmaps::navmap->new(); $r->print(''
.&mt('Instructors may use a reservation system to place restrictions on when and where assignments can be worked on.')
.'
'
.&mt('One example is for management of laboratory space, which is only available at certain times, and has a limited number of seats.')
.'
'. &mt('Your reservation status for any such assignments is listed below:'). '
'. ''.$spacers.''. $icon.(' ' x6).' | '."\n"; if (ref($output{$currmap}) eq 'HASH') { my $formnum = $mapnum.'_'.$reservable+1; my $class = 'LC_slotmaptext_'.$mapnum; if ($output{$currmap}{'hasaction'}) { $row .= ''. $output{$currmap}{'msg'}. ' | '. &slot_chooser($repsymbs{$currmap},$class,$formnum, $allavailable,$slots,$consumed_uniqueperiods). ' | '; } else { $row .= ''. $output{$currmap}{'msg'}. ' | '; } $row .= ''."\n"; } } else { my ($spacers,$icon) = &show_map_row($depth,$location,$currmaptype,$currmaptitle); $row .= ' | '.$spacers.$icon.(' ' x6).' | '."\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 ++; if (!$shownheader) { $r->print($slotheader); $shownheader = 1; } my $style; if (exists($output{$currmap})) { $style = 'none'; } else { $style = 'table-row'; $shown ++; } my $title = $resource->compTitle(); my $bgcolor = $backgrounds[$shown % $numcolors]; $r->print(' |
'); for (my $i=0; $i<$depth; $i++) { $r->print(''); } $r->print(''. ''.$title.''.(' ' x6).' | '); my $class = 'LC_slottext_'.$mapnum; if ($output{$symb}{'hasaction'}) { $r->print(''.$output{$symb}{'msg'}.' | '. ''. &slot_chooser($symb,$class,$reservable,$allavailable,$slots, $consumed_uniqueperiods).' | '); } else { $r->print(''. ''.$output{$symb}{'msg'}.''. ' | '); } $r->print('
'. &mt('Reservation History').'
'); } sub show_map_row { my ($depth,$location,$type,$title) = @_; my $spacers; for (my $i=0; $i<$depth-1; $i++) { $spacers .= ''; } my $icon; if ($type eq 'page') { $icon = ' '."\n"; } else { $icon = ' '."\n"; } $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 = ''. &mt('An error occurred determining slot availability.'). ''; } 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 { my ($r,$uname,$udom) = @_; if (!defined($uname)) { $uname = $env{'user.name'}; } if (!defined($udom)) { $udom = $env{'user.domain'}; } my $formname = 'slotlog'; 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') { $r->print(''. &mt('Return to slot list').'
'); return; } my $formname = 'reservationslog'; my ($cnum,$cdom)=&get_course(); my %slotlog=&Apache::lonnet::dump('nohist_slotreservationslog',$cdom,$cnum); if ((keys(%slotlog))[0]=~/^error\:/) { undef(%slotlog); } my (%log,@allsymbs); if (keys(%slotlog)) { foreach my $key (keys(%slotlog)) { if (ref($slotlog{$key}) eq 'HASH') { if (ref($slotlog{$key}{'logentry'}) eq 'HASH') { if ($slotlog{$key}{'logentry'}{'slot'} eq $env{'form.slotname'}) { $log{$key} = $slotlog{$key}; if ($slotlog{$key}{'logentry'}{'symb'} ne '') { push(@allsymbs,$slotlog{$key}{'logentry'}{'symb'}); } } } } } } $r->print(''. ''. &mt('Return to slot list').'
'); return; } sub get_resource_title { my ($symb,$titles,$maptitles) = @_; my $title; if ((ref($titles) eq 'HASH') && (ref($maptitles) eq 'HASH')) { if (defined($titles->{$symb})) { $title = $titles->{$symb}; } else { $title = &Apache::lonnet::gettitle($symb); my $maptitle; my ($mapurl) = &Apache::lonnet::decode_symb($symb); if (defined($maptitles->{$mapurl})) { $maptitle = $maptitles->{$mapurl}; } else { if ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) { $maptitle=&mt('Main Content'); } else { $maptitle=&Apache::lonnet::gettitle($mapurl); } $maptitles->{$mapurl} = $maptitle; } if ($maptitle ne '') { $title .= ' '.&mt('(in [_1])',$maptitle); } $titles->{$symb} = $title; } } else { $title = $symb; } return $title; } sub reservationlog_contexts { my ($crstype) = @_; my %lt = &Apache::lonlocal::texthash ( any => 'Any', user => 'By student', manage => 'Via Slot Manager', parameter => 'Via Parameter Manager', reserve => 'Made reservation', release => 'Dropped reservation', usermanage => 'By student', ); if ($crstype eq 'Community') { $lt{'user'} = &mt('By member'); $lt{'usermanage'} = $lt{'user'}; } return %lt; } sub display_filter { my ($formname,$cdom,$cnum,$curr,$version,$allsymbs) = @_; my $nolink = 1; my (%titles,%maptitles); my $output = ''.
''.&mt('Changes/page:').' '. &Apache::lonmeta::selectbox('show',$curr->{'show'},'',undef, (&mt('all'),5,10,20,50,100,1000,10000)). ' | '; my $startform = &Apache::lonhtmlcommon::date_setter($formname,'log_start_date', $curr->{'log_start_date'},undef, undef,undef,undef,undef,undef,undef,$nolink); my $endform = &Apache::lonhtmlcommon::date_setter($formname,'log_end_date', $curr->{'log_end_date'},undef, undef,undef,undef,undef,undef,undef,$nolink); my $crstype = &Apache::loncommon::course_type(); my %lt = &reservationlog_contexts($crstype); $output .= ' | '.&mt('Window during which changes occurred:').
'
| '; if (ref($allsymbs) eq 'ARRAY') { $output .= ' | '.&mt('Resource').' '. ' | '.
&mt('Context:').' | ';
} else {
$output .= ''.&mt('Action').' '. ' | ';
}
$output .= '
'. &mt('Only changes made from servers running LON-CAPA [_1] or later are displayed.' ,'2.9.0'); if ($version) { $output .= ' '.&mt('This LON-CAPA server is version [_1]',$version); } $output .= '
'.&mt('Created [quant,_1,slot]',$countdone)."\n".'
'); foreach my $error (@errors) { $r->print(''.$error.'
'."\n"); } &show_table($r,$mgr); return ''; } sub slot_command_titles { my %titles = ( slotlog => 'Reservation Logs', showslots => 'Manage Slots', showresv => 'Reservation History', manageresv => 'Manage Reservations', uploadstart => 'Upload Slots File', csvuploadmap => 'Upload Slots File', csvuploadassign => 'Upload Slots File', delete => 'Slot Deletion', release => 'Reservation Result', remove_reservation => 'Remove Registration', get_reservation => 'Request Reservation', ); return %titles; } sub slot_reservationmsg_options { my %options = &Apache::lonlocal::texthash ( only_student => 'Sent to student', student_and_user_notes_screen => 'Sent to student and added to user notes', none => 'None sent and no record in user notes', ); return %options; } sub handler { my $r=shift; &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); if ($r->header_only()) { $r->send_http_header(); return OK; } &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); my %crumb_titles = &slot_command_titles(); my ($brcrum,$bread_crumbs_component); 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'; } } elsif ($env{'form.command'} eq 'manageresv') { if (($vgr eq 'F') || ($mgr eq 'F')) { $env{'form.command'} = 'showslots'; } } my $title='Requesting Another Worktime'; if ($env{'form.command'} eq 'showresv') { $title = 'Reservation History'; if ($env{'form.origin'} eq 'aboutme') { $brcrum =[{href=>"/adm/$env{'form.udom'}/$env{'form.uname'}/aboutme",text=>'Personal Information Page'}]; } else { $brcrum =[{href=>"/adm/slotrequest?command=manageresv",text=>'Manage Reservations'}]; } if (ref($brcrum) eq 'ARRAY') { push(@{$brcrum},{href=>"/adm/slotrequest?command=showresv",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", text=>$crumb_titles{'showslots'}, help=>'Slot_Use'}]; $title = 'Managing Slots'; $bread_crumbs_component = 'Slots'; unless ($env{'form.command'} eq 'showslots') { if (ref($brcrum) eq 'ARRAY') { push(@{$brcrum},{href=>"/adm/slotrequest?command=$env{'form.command'}",text=>$crumb_titles{$env{'form.command'}}}); } } } } elsif ($env{'form.command'} eq 'release') { if ($env{'form.context'} eq 'usermanage') { $brcrum =[{href=>"/adm/slotrequest?command=manageresv", text=>$crumb_titles{'showslots'}}]; $title = 'Manage Reservations'; if (ref($brcrum) eq 'ARRAY') { push(@{$brcrum},{href=>"/adm/slotrequest?command=$env{'form.command'}",text=>$crumb_titles{$env{'form.command'}}}); } } } else { $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,$bread_crumbs_component,$js,$mgr); if ($env{'form.command'} eq 'manageresv') { $allavailable = $available; undef($available); undef($got_slots); my $crstype = &Apache::loncommon::course_type(); &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') { &show_table($r,$mgr); } elsif ($env{'form.command'} eq 'remove_registration' && $mgr eq 'F') { &remove_registration($r); } elsif ($env{'form.command'} eq 'release' && $mgr eq 'F') { if ($env{'form.entry'} eq 'remove all') { &release_all_slot($r,$mgr); } else { &release_slot($r,undef,undef,undef,$mgr); } } elsif ($env{'form.command'} eq 'delete' && $mgr eq 'F') { &delete_slot($r); } elsif ($env{'form.command'} eq 'uploadstart' && $mgr eq 'F') { &upload_start($r); } elsif ($env{'form.command'} eq 'csvuploadmap' && $mgr eq 'F') { &csv_upload_map($r); } elsif ($env{'form.command'} eq 'csvuploadassign' && $mgr eq 'F') { if ($env{'form.associate'} ne 'Reverse Association') { &csv_upload_assign($r,$mgr); } else { if ( $env{'form.upfile_associate'} ne 'reverse' ) { $env{'form.upfile_associate'} = 'reverse'; } else { $env{'form.upfile_associate'} = 'forward'; } &csv_upload_map($r); } } elsif (($env{'form.command'} eq 'slotlog') && ($vgr eq 'F')) { &show_reservations_log($r); } else { my $symb=&unescape($env{'form.symb'}); if (!defined($symb)) { &fail($r,'not_valid'); return OK; } my (undef,undef,$res)=&Apache::lonnet::decode_symb($symb); my $useslots = &Apache::lonnet::EXT("resource.0.useslots",$symb); if ($useslots ne 'resource' && $useslots ne 'map' && $useslots ne 'map_map') { &fail($r,'not_available'); return OK; } $env{'request.symb'}=$symb; my $type = ($res =~ /\.task$/) ? 'Task' : 'problem'; 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 'NEED_DIFFERENT_IP') { &fail($r,'not_allowed'); return OK; } if ($env{'form.requestattempt'}) { $r->print(''.&mt('Unknown command: [_1]',$env{'form.command'}).'
'); } } &end_page($r); return OK; } 1; __END__