# The LearningOnline Network with CAPA # Handler for requesting to have slots added to a students record # # $Id: slotrequest.pm,v 1.79 2007/04/11 21:49:02 albertel 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; 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)=@_; $r->print(&Apache::loncommon::start_page($title)); } sub end_page { my ($r)=@_; $r->print(&Apache::loncommon::end_page()); } =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; foreach my $slot_name (sort { if (ref($slots{$a}) && ref($slots{$b})) { return $slots{$a}{'starttime'} <=> $slots{$b}{'starttime'} } if (ref($slots{$a})) { return -1;} if (ref($slots{$b})) { return 1;} return 0; } @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'}); if ($slots{$slot_name}->{'endtime'} > time && $slots{$slot_name}->{'startreserve'} < time) { # between start of reservation times and 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; my @problems = $navmap->retrieveResources(undef, sub { $_[0]->is_problem() },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)) { $consumed_uniqueperiods = &get_consumed_uniqueperiods($slots); if (&Apache::lonnet::error(%$consumed_uniqueperiods)) { 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)=@_; my ($cnum,$cdom)=&get_course(); my $value=&Apache::lonnet::EXT("resource.0.availablestudent",$symb, $env{'user.domain'},$env{'user.name'}); &Apache::lonxml::debug("value is $value
"); my $use_slots = &Apache::lonnet::EXT("resource.0.useslots"); &Apache::lonxml::debug("use_slots is $use_slots
"); if (&Apache::lonnet::error($value) || &Apache::lonnet::error($use_slots)) { return 'error: Unable to determine current status'; } my $parm_symb = $symb; my $parm_level = 1; if ($use_slots eq 'map' || $use_slots eq 'map_map') { my ($map) = &Apache::lonnet::decode_symb($symb); $parm_symb = &Apache::lonnet::symbread($map); $parm_level = 2; } foreach my $other_slot (split(/:/, $value)) { if ($other_slot eq $slot_name) { my %consumed=&Apache::lonnet::dump('slot_reservations', $cdom, $cnum, "^$slot_name\0"); if (&Apache::lonnet::error($value)) { return 'error: Unable to determine current status'; } my $me=$env{'user.name'}.':'.$env{'user.domain'}; foreach my $key (keys(%consumed)) { if ($consumed{$key}->{'name'} eq $me) { my $num=(split('\0',$key))[1]; return -$num; } } } } my $max=$slot->{'maxspace'}; if (!defined($max)) { $max=99999; } my (@ids)=&get_reservation_ids($slot_name); if (&Apache::lonnet::error(@ids)) { return 'error: Unable to determine current status'; } my $last=0; foreach my $id (@ids) { my $num=(split('\0',$id))[1]; if ($num > $last) { $last=$num; } } my $wanted=$last+1; &Apache::lonxml::debug("wanted $wanted
"); if (scalar(@ids) >= $max) { # full up return undef; } my %reservation=('name' => $env{'user.name'}.':'.$env{'user.domain'}, 'timestamp' => time, 'symb' => $parm_symb); my $success=&Apache::lonnet::newput('slot_reservations', {"$slot_name\0$wanted" => \%reservation}, $cdom, $cnum); if ($success eq 'ok') { my $new_value=$slot_name; if ($value) { $new_value=$value.':'.$new_value; } my $result=&Apache::lonparmset::storeparm_by_symb($symb, '0_availablestudent', $parm_level, $new_value, 'string', $env{'user.name'}, $env{'user.domain'}); &Apache::lonxml::debug("hrrm $result"); return $wanted; } # someone else got it return undef; } sub remove_registration { my ($r) = @_; if ($env{'form.entry'} ne 'remove all') { return &remove_registration_user($r); } my $slot_name = $env{'form.slotname'}; my %slot=&Apache::lonnet::get_slot($slot_name); my ($cnum,$cdom)=&get_course(); my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); if (&Apache::lonnet::error(%consumed)) { $r->print("

".&mt('A network error has occured.').'

'); 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']); } 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']); } 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

$hidden_input
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); $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); $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); if ($mgr ne 'F') { if ($slot{'starttime'} < time) { return (0,&mt('Not allowed to release Reservation: [_1], as it has already ended.',$description)); } } # get parameter string, check for existance, rebuild string with the slot my @slots = split(/:/,&Apache::lonnet::EXT("resource.0.availablestudent", $symb,$udom,$uname)); my @new_slots; foreach my $exist_slot (@slots) { if ($exist_slot eq $slot_name) { next; } push(@new_slots,$exist_slot); } my $new_param = join(':',@new_slots); my ($cnum,$cdom)=&get_course(); # get slot reservations, check if user has one, if so remove 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) ) { &Apache::lonnet::del('slot_reservations',[$entry], $cdom,$cnum); } } my $use_slots = &Apache::lonnet::EXT("resource.0.useslots"); &Apache::lonxml::debug("use_slots is $use_slots
"); 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', $parm_level, $new_param, 'string', $uname, $udom); my $msg; if ($mgr eq 'F') { $msg = &mt('Released Reservation for user: [_1]',"$uname:$udom"); } else { $msg = &mt('Released Reservation: [_1]',$description); } return (1,$msg); } sub delete_slot { my ($r)=@_; my $slot_name = $env{'form.slotname'}; my %slot=&Apache::lonnet::get_slot($slot_name); my ($cnum,$cdom)=&get_course(); my %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum, "^$slot_name\0"); my ($tmp) = %consumed; if ($tmp =~ /error: 2/) { undef(%consumed); } if (%slot && !%consumed) { $slot{'type'} = 'deleted'; my $ret = &Apache::lonnet::cput('slots', {$slot_name => \%slot}, $cdom, $cnum); if ($ret eq 'ok') { $r->print("

Slot $slot_name marked as deleted.

"); } else { $r->print("

An error ($ret) occurse when attempting to delete Slot $slot_name.

"); } } else { if (%consumed) { $r->print("

Slot $slot_name has active reservations.

"); } else { $r->print("

Slot $slot_name does not exist.

"); } } $r->print('

'. &mt('Return to slot list').'

'); &return_link($r); } sub return_link { my ($r) = @_; $r->print('

'. &mt('Return to last resource').'

'); } 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("

An error occured while attempting to make a reservation. ($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); %slot=&Apache::lonnet::get_slot($env{'form.slotname'}); my $description2=&get_description($env{'form.slotname'},\%slot); $r->print("

Already have a reservation: $description1

"); if ($slot_name ne $env{'form.slotname'}) { $r->print(< STUFF $r->print("

You can either "); $r->print(< STUFF $r->print(' your reservation from '.$description1.' to '. $description2. '
or

'); &return_link($r); $r->print(< STUFF } else { &return_link($r); } return 0; } my $reserved=&make_reservation($env{'form.slotname'}, \%slot,$symb); my $description=&get_description($env{'form.slotname'},\%slot); if (defined($reserved)) { my $retvalue = 0; if ($slot_name =~ /^error: (.*)/) { $r->print("

An error occured while attempting to make a reservation. ($1)

"); } elsif ($reserved > -1) { $r->print("

Success: $description

"); $retvalue = 1; } elsif ($reserved < 0) { $r->print("

Already reserved: $description

"); } if (!$inhibit_return_link) { &return_link($r); } return 1; } my %lt=('request'=>"Availibility list", 'try' =>'Try again'); %lt=&Apache::lonlocal::texthash(%lt); my $extra_input; if ($conflictable_slot) { $extra_input=''; } $r->print(< Failed to reserve a spot for $description.

$extra_input
?

or

or STUFF if (!$inhibit_return_link) { &return_link($r); } return 0; } sub allowed_slot { my ($slot_name,$slot,$symb,$slots,$consumed_uniqueperiods)=@_; #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; } &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'}) && $slot->{'symb'} ne $symb) { return 0; } my $conflict = &check_for_conflict($symb,$slot_name,$slot,$slots, $consumed_uniqueperiods); if ($conflict) { 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 ($r,$symb)=@_; my ($cnum,$cdom)=&get_course(); my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); my $consumed_uniqueperiods = &get_consumed_uniqueperiods(\%slots); my $available; $r->print(''); &Apache::lonxml::debug("Checking Slots"); my @got_slots=&check_for_reservation($symb,'allslots'); 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},undef,\%slots, $consumed_uniqueperiods)); $available++; my $description=&get_description($slot,$slots{$slot}); my $form=&mt('Unavailable'); 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)) { $text=&mt('Drop Reservation'); $command='release'; } else { my $conflict = &check_for_conflict($symb,$slot,$slots{$slot}, \%slots, $consumed_uniqueperiods); if ($conflict) { $text=&mt('Change Reservation'); $command='get'; } } my $escsymb=&escape($symb); $form=< STUFF } $r->print(< STUFF } if (!$available) { $r->print(''); } $r->print('
$form $description
No available times. '. &mt('Return to last resource').'
'); } sub to_show { my ($slotname,$slot,$when,$deleted,$name) = @_; my $time=time; my $week=60*60*24*7; if ($deleted eq 'hide' && $slot->{'type'} eq 'deleted') { return 0; } if ($name && $name->{'value'} =~ /\w/) { if ($name->{'type'} eq 'substring') { if ($slotname !~ /\Q$name->{'value'}\E/) { return 0; } } if ($name->{'type'} eq 'exact') { if ($slotname eq $name->{'value'}) { return 0; } } } if ($when eq 'any') { return 1; } elsif ($when eq 'now') { if ($time > $slot->{'starttime'} && $time < $slot->{'endtime'}) { return 1; } return 0; } elsif ($when eq 'nextweek') { if ( ($time < $slot->{'starttime'} && ($time+$week) > $slot->{'starttime'}) || ($time < $slot->{'endtime'} && ($time+$week) > $slot->{'endtime'}) ) { return 1; } return 0; } elsif ($when eq 'lastweek') { if ( ($time > $slot->{'starttime'} && ($time-$week) < $slot->{'starttime'}) || ($time > $slot->{'endtime'} && ($time-$week) < $slot->{'endtime'}) ) { return 1; } return 0; } elsif ($when eq 'willopen') { if ($time < $slot->{'starttime'}) { return 1; } return 0; } elsif ($when eq 'wereopen') { if ($time > $slot->{'endtime'}) { return 1; } return 0; } return 1; } sub remove_link { my ($slotname,$entry,$uname,$udom,$symb) = @_; my $remove = &mt('Remove'); if ($entry eq 'remove all') { $remove = &mt('Remove All'); undef($uname); undef($udom); } $slotname = &escape($slotname); $entry = &escape($entry); $uname = &escape($uname); $udom = &escape($udom); $symb = &escape($symb); return <<"END_LINK"; ($remove) END_LINK } sub show_table { my ($r,$mgr)=@_; my ($cnum,$cdom)=&get_course(); my %slots=&Apache::lonnet::dump('slots',$cdom,$cnum); if ( (keys(%slots))[0] =~ /^error: 2 /) { undef(%slots); } my $available; if ($mgr eq 'F') { # FIXME: This line should be deleted once Slots uses breadcrumbs $r->print(&Apache::loncommon::help_open_topic('Slot About', 'Help on slots')); $r->print('
'); $r->print('
'); $r->print(&Apache::loncommon::help_open_topic('Slot CommaDelimited')); $r->print('
'); $r->print(&Apache::loncommon::help_open_topic('Slot AddInterface')); $r->print('
'); } 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', 'secret' => 'Secret Word', 'space' => '# of students/max', 'ip' => 'IP or DNS restrictions', 'symb' => 'Resource 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'); my @show_order=('name','description','type','starttime','endtime', 'startreserve','secret','space','ip','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('
'); $r->print('
'); $r->print('
'.&mt('Show').' '.&mt('Student Display').' '.&mt('Open').' '.&mt('Slot Name Filter').' '.&mt('Options').'
'.&Apache::loncommon::multiple_select_form('show',\@show,6,\%show_fields,\@show_order). ' '.&Apache::loncommon::multiple_select_form('studisplay',\@stu_display, 6,\%stu_display_fields, \@stu_display_order).' '.&Apache::loncommon::select_form($when,'when',%when_fields). ' '.&Apache::loncommon::select_form($name_filter_type, 'name_filter_type', %name_filter_type_fields). '
'. &Apache::lonhtmlcommon::textbox('name_filter_value', $env{'form.name_filter_value'}, 15). '
Deleted slots:
'); $r->print('
'); $r->print('

'); my $linkstart=''.$show_fields{$which}.''); } } $r->print(&Apache::loncommon::end_data_table_header_row()); my %name_cache; my $slotsort = sub { if ($env{'form.order'}=~/^(type|description|endtime|startreserve|ip|symb|allowedsections|allowedusers)$/) { if (lc($slots{$a}->{$env{'form.order'}}) ne lc($slots{$b}->{$env{'form.order'}})) { return (lc($slots{$a}->{$env{'form.order'}}) cmp lc($slots{$b}->{$env{'form.order'}})); } } elsif ($env{'form.order'} eq 'space') { if ($slots{$a}{'maxspace'} ne $slots{$b}{'maxspace'}) { return ($slots{$a}{'maxspace'} cmp $slots{$b}{'maxspace'}); } } elsif ($env{'form.order'} eq 'name') { if (lc($a) cmp lc($b)) { return lc($a) cmp lc($b); } } elsif ($env{'form.order'} eq 'uniqueperiod') { if ($slots{$a}->{'uniqueperiod'}[0] ne $slots{$b}->{'uniqueperiod'}[0]) { return ($slots{$a}->{'uniqueperiod'}[0] cmp $slots{$b}->{'uniqueperiod'}[0]); } if ($slots{$a}->{'uniqueperiod'}[1] ne $slots{$b}->{'uniqueperiod'}[1]) { return ($slots{$a}->{'uniqueperiod'}[1] cmp $slots{$b}->{'uniqueperiod'}[1]); } } return $slots{$a}->{'starttime'} <=> $slots{$b}->{'starttime'}; }; my %consumed; if (exists($show{'scheduled'}) || exists($show{'space'}) ) { %consumed=&Apache::lonnet::dump('slot_reservations',$cdom,$cnum); my ($tmp)=%consumed; if ($tmp =~ /^error: /) { undef(%consumed); } } foreach my $slot (sort $slotsort (keys(%slots))) { if (!&to_show($slot,$slots{$slot},$when, $env{'form.deleted'},$name_filter)) { next; } if (defined($slots{$slot}->{'type'}) && $slots{$slot}->{'type'} ne 'schedulable_student') { #next; } my $description=&get_description($slot,$slots{$slot}); my ($id_count,$ids); if (exists($show{'scheduled'}) || exists($show{'space'}) ) { my $re_str = "$slot\0"; my @this_slot = grep(/^\Q$re_str\E/,keys(%consumed)); $id_count = scalar(@this_slot); if (exists($show{'scheduled'})) { foreach my $entry (sort { $consumed{$a}{name} cmp $consumed{$b}{name} } (@this_slot)) { my (undef,$id)=split("\0",$entry); my ($uname,$udom) = split(':',$consumed{$entry}{'name'}); $ids.= ''; foreach my $item (@stu_display_order) { if ($stu_display{$item}) { if ($item eq 'fullname') { $ids.=$fullname->{"$uname:$udom"}.' '; } elsif ($item eq 'username') { $ids.="$uname:$udom "; } } } $ids.=&remove_link($slot,$entry,$uname,$udom, $consumed{$entry}{'symb'}).'
'; } } } my $start=($slots{$slot}->{'starttime'}? &Apache::lonlocal::locallocaltime($slots{$slot}->{'starttime'}):''); my $end=($slots{$slot}->{'endtime'}? &Apache::lonlocal::locallocaltime($slots{$slot}->{'endtime'}):''); my $start_reserve=($slots{$slot}->{'startreserve'}? &Apache::lonlocal::locallocaltime($slots{$slot}->{'startreserve'}):''); my $unique; if (ref($slots{$slot}{'uniqueperiod'})) { $unique=localtime($slots{$slot}{'uniqueperiod'}[0]).', '. localtime($slots{$slot}{'uniqueperiod'}[1]); } my $title; 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=''.$title.''; } my $allowedsections; if (exists($show{'allowedsections'})) { $allowedsections = join(', ',sort(split(/\s*,\s*/, $slots{$slot}->{'allowedsections'}))); } my @allowedusers; if (exists($show{'allowedusers'})) { @allowedusers= map { my ($uname,$udom)=split(/:/,$_); my $fullname=$name_cache{$_}; if (!defined($fullname)) { $fullname = &Apache::loncommon::plainname($uname,$udom); $fullname =~s/\s/ /g; $name_cache{$_} = $fullname; } &Apache::loncommon::aboutmewrapper($fullname,$uname,$udom); } (sort(split(/\s*,\s*/,$slots{$slot}->{'allowedusers'}))); } my $allowedusers=join(', ',@allowedusers); my @proctors; my $rowspan=1; my $colspan=1; if (exists($show{'proctor'})) { $rowspan=2; @proctors= map { my ($uname,$udom)=split(/:/,$_); my $fullname=$name_cache{$_}; if (!defined($fullname)) { $fullname = &Apache::loncommon::plainname($uname,$udom); $fullname =~s/\s/ /g; $name_cache{$_} = $fullname; } &Apache::loncommon::aboutmewrapper($fullname,$uname,$udom); } (sort(split(/\s*,\s*/,$slots{$slot}->{'proctor'}))); } my $proctors=join(', ',@proctors); my $edit=(<<"EDITLINK"); Edit EDITLINK my $delete=(<<"DELETELINK"); Delete DELETELINK my $remove_all=&remove_link($slot,'remove all').'
'; if ($ids ne '') { undef($delete); } if ($slots{$slot}{'type'} ne 'schedulable_student' || $ids eq '') { undef($remove_all); } my $row_start=&Apache::loncommon::start_data_table_row(); my $row_end=&Apache::loncommon::end_data_table_row(); $r->print($row_start. "\n$edit $delete\n"); if (exists($show{'name'})) { $colspan++;$r->print("$slot"); } if (exists($show{'description'})) { $colspan++;$r->print("$description\n"); } if (exists($show{'type'})) { $colspan++;$r->print("$slots{$slot}->{'type'}\n"); } if (exists($show{'starttime'})) { $colspan++;$r->print("$start\n"); } if (exists($show{'endtime'})) { $colspan++;$r->print("$end\n"); } if (exists($show{'startreserve'})) { $colspan++;$r->print("$start_reserve\n"); } if (exists($show{'secret'})) { $colspan++;$r->print("$slots{$slot}{'secret'}\n"); } if (exists($show{'space'})) { my $display = $id_count; if ($slots{$slot}{'maxspace'}>0) { $display.='/'.$slots{$slot}{'maxspace'}; if ($slots{$slot}{'maxspace'} <= $id_count) { $display = ''.$display.' (full) '; } } $colspan++;$r->print("$display\n"); } if (exists($show{'ip'})) { $colspan++;$r->print("$slots{$slot}{'ip'}\n"); } if (exists($show{'symb'})) { $colspan++;$r->print("$title\n"); } if (exists($show{'allowedsections'})) { $colspan++;$r->print("$allowedsections\n"); } if (exists($show{'allowedusers'})) { $colspan++;$r->print("$allowedusers\n"); } if (exists($show{'uniqueperiod'})) { $colspan++;$r->print("$unique\n"); } if (exists($show{'scheduled'})) { $colspan++;$r->print("$remove_all $ids\n"); } $r->print("$row_end\n"); if (exists($show{'proctor'})) { $r->print(<$proctors $row_end STUFF } } $r->print(''); } sub upload_start { my ($r)=@_; $r->print(&Apache::grades::checkforfile_js()); my $result.=''."\n"; $result.='
'."\n"; $result.=' '. &mt('Specify a file containing the slot definitions.'). '
'."\n"; my $upfile_select=&Apache::loncommon::upfile_select_html(); my $ignore=&mt('Ignore First Line'); $result.=< $upfile_select
ENDUPFORM $result.='
'."\n"; $result.=''."\n"; $r->print($result); } sub csvuploadmap_header { my ($r,$datatoken,$distotal)= @_; my $javascript; if ($env{'form.upfile_associate'} eq 'reverse') { $javascript=&csvupload_javascript_reverse_associate(); } else { $javascript=&csvupload_javascript_forward_associate(); } my $checked=(($env{'form.noFirstLine'})?' checked="checked"':''); my $ignore=&mt('Ignore First Line'); my $help_field = &Apache::loncommon::help_open_topic('Slot SelectingField'); $r->print(<

Identify fields $help_field

Total number of records found in file: $distotal
Enter as many fields as you can. The system will inform you and bring you back to this page if the data selected is insufficient to create the slots.

ENDPICK return ''; } sub csvuploadmap_footer { my ($request,$i,$keyfields) =@_; $request->print(<
ENDPICK } sub csvupload_javascript_reverse_associate { my $error1=&mt('You need to specify the name, starttime, endtime and a type'); return(<[0].','; } chop($keyfields); } else { unshift(@fields,['none','']); $i=&Apache::loncommon::csv_samples_select_table($r,\@records, \@fields); my %sone=&Apache::loncommon::record_sep($records[0]); $keyfields=join(',',sort(keys(%sone))); } } &csvuploadmap_footer($r,$i,$keyfields); return ''; } sub csvupload_fields { return (['name','Slot name'], ['type','Type of slot'], ['starttime','Start Time of slot'], ['endtime','End Time of slot'], ['startreserve','Reservation Start Time'], ['ip','IP or DNS restriction'], ['proctor','List of proctor ids'], ['description','Slot Description'], ['maxspace','Maximum number of reservations'], ['symb','Resource Restriction'], ['uniqueperiod','Date range of slot exclusion'], ['secret','Secret word proctor uses to validate'], ['allowedsections','Sections slot is restricted to'], ['allowedusers','Users slot is restricted to'], ); } sub csv_upload_assign { my ($r,$mgr)= @_; &Apache::loncommon::load_tmp_file($r); my @slotdata = &Apache::loncommon::upfile_record_sep(); if ($env{'form.noFirstLine'}) { shift(@slotdata); } my %fields=&Apache::grades::get_fields(); $r->print('

Creating Slots

'); my $cname=$env{'course.'.$env{'request.course.id'}.'.num'}; my $cdom=$env{'course.'.$env{'request.course.id'}.'.domain'}; my $countdone=0; my @errors; foreach my $slot (@slotdata) { my %slot; my %entries=&Apache::loncommon::record_sep($slot); my $domain; my $name=$entries{$fields{'name'}}; if ($name=~/^\s*$/) { push(@errors,"Did not create slot with no name"); next; } if ($name=~/\s/) { push(@errors,"$name not created -- Name must not contain spaces"); next; } if ($name=~/\W/) { push(@errors,"$name not created -- Name must contain only letters, numbers and _"); next; } if ($entries{$fields{'type'}}) { $slot{'type'}=$entries{$fields{'type'}}; } else { $slot{'type'}='preassigned'; } if ($slot{'type'} ne 'preassigned' && $slot{'type'} ne 'schedulable_student') { push(@errors,"$name not created -- invalid type ($slot{'type'}) must be either preassigned or schedulable_student"); next; } if ($entries{$fields{'starttime'}}) { $slot{'starttime'}=&UnixDate($entries{$fields{'starttime'}},"%s"); } if ($entries{$fields{'endtime'}}) { $slot{'endtime'}=&UnixDate($entries{$fields{'endtime'}},"%s"); } # start/endtime must be defined and greater than zero if (!$slot{'starttime'}) { push(@errors,"$name not created -- Invalid start time"); next; } if (!$slot{'endtime'}) { push(@errors,"$name not created -- Invalid end time"); next; } if ($slot{'starttime'} > $slot{'endtime'}) { push(@errors,"$name not created -- Slot starts after it ends"); next; } if ($entries{$fields{'startreserve'}}) { $slot{'startreserve'}= &UnixDate($entries{$fields{'startreserve'}},"%s"); } if (defined($slot{'startreserve'}) && $slot{'startreserve'} > $slot{'starttime'}) { push(@errors,"$name not created -- Slot's reservation start time is after the slot's start time."); next; } foreach my $key ('ip','proctor','description','maxspace', 'secret','symb') { if ($entries{$fields{$key}}) { $slot{$key}=$entries{$fields{$key}}; } } if ($entries{$fields{'uniqueperiod'}}) { my ($start,$end)=split(',',$entries{$fields{'uniqueperiod'}}); my @times=(&UnixDate($start,"%s"), &UnixDate($end,"%s")); $slot{'uniqueperiod'}=\@times; } if (defined($slot{'uniqueperiod'}) && $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; } &Apache::lonnet::cput('slots',{$name=>\%slot},$cdom,$cname); $r->print('.'); $r->rflush(); $countdone++; } $r->print("

Created $countdone slots\n

"); foreach my $error (@errors) { $r->print("

$error

\n"); } &show_table($r,$mgr); return ''; } 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 $vgr=&Apache::lonnet::allowed('vgr',$env{'request.course.id'}); my $mgr=&Apache::lonnet::allowed('mgr',$env{'request.course.id'}); my $title='Requesting Another Worktime'; if ($env{'form.command'} =~ /^(showslots|uploadstart|csvuploadmap|csvuploadassign)$/ && $vgr eq 'F') { $title = 'Managing Slots'; } &start_page($r,$title); if ($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); } } 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') { &fail($r,'not_allowed'); return OK; } if ($env{'form.requestattempt'}) { &show_choices($r,$symb); } elsif ($env{'form.command'} eq 'release') { &release_slot($r,$symb); } elsif ($env{'form.command'} eq 'get') { &get_slot($r,$symb); } elsif ($env{'form.command'} eq 'change') { if (&get_slot($r,$symb,$env{'form.releaseslot'},1)) { &release_slot($r,$symb,$env{'form.releaseslot'}); } } else { $r->print("

Unknown command: ".$env{'form.command'}."

"); } } &end_page($r); return OK; } 1; __END__