--- loncom/interface/lonrequestcourse.pm 2009/08/25 14:59:53 1.25 +++ loncom/interface/lonrequestcourse.pm 2009/10/24 03:24:13 1.37 @@ -1,7 +1,7 @@ # The LearningOnline Network # Request a course # -# $Id: lonrequestcourse.pm,v 1.25 2009/08/25 14:59:53 raeburn Exp $ +# $Id: lonrequestcourse.pm,v 1.37 2009/10/24 03:24:13 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -44,24 +44,28 @@ described at http://www.lon-capa.org. =item handler() +=item get_breadcrumbs() + =item header() =item form_elements() =item onload_action() -=item check_can_request() - -=item course_types() - =item print_main_menu() =item request_administration() +=item close_popup_form() + +=item get_instcode() + =item print_request_form() =item print_enrollment_menu() +=item show_invalid_crosslists() + =item inst_section_selector() =item date_setting_table() @@ -94,12 +98,14 @@ described at http://www.lon-capa.org. =item check_autolimit() -=item build_batchcreatehash() - =item retrieve_settings() =item get_request_settings() +=item extract_instcode() + +=item generate_date_items() + =back =cut @@ -112,6 +118,7 @@ use Apache::lonnet; use Apache::loncommon; use Apache::lonlocal; use Apache::loncoursequeueadmin; +use Apache::lonuserutils; use LONCAPA qw(:DEFAULT :match); sub handler { @@ -122,38 +129,16 @@ sub handler { return OK; } + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['action','showdom','cnum','state']); &Apache::lonhtmlcommon::clear_breadcrumbs(); my $dom = &get_course_dom(); my $action = $env{'form.action'}; my $state = $env{'form.state'}; - my %stored; - my $jscript; - if ((defined($state)) && (defined($action))) { - if (($action eq 'view') && ($state eq 'details')) { - if ((defined($env{'form.showdom'})) && (defined($env{'form.cnum'}))) { - my $result = &retrieve_settings($env{'form.showdom'},$env{'form.cnum'}); - } - } - my %elements = &form_elements($dom); - my $elementsref = {}; - if (ref($elements{$action}) eq 'HASH') { - if (ref($elements{$action}{$state}) eq 'HASH') { - $elementsref = $elements{$action}{$state}; - } - } - if (($state eq 'courseinfo') && ($env{'form.clonedom'} eq '')) { - $env{'form.clonedom'} = $dom; - } - $jscript = &Apache::lonhtmlcommon::set_form_elements($elementsref,\%stored); - } - - if ($state eq 'personnel') { - $jscript .= "\n".&Apache::loncommon::userbrowser_javascript(); - } + my (%states,%stored); + my ($jscript,$uname,$udom,$result,$warning); - my $loaditems = &onload_action($action,$state); - - my %states; + $states{'display'} = ['details']; $states{'view'} = ['pick_request','details','cancel','removal']; $states{'log'} = ['filter','display']; $states{'new'} = ['courseinfo','enrollment','personnel','review','process']; @@ -170,6 +155,7 @@ sub handler { } } + my @invalidcrosslist; my %trail = ( crstype => 'Course Request Action', codepick => 'Category', @@ -186,56 +172,118 @@ sub handler { if (($env{'form.crstype'} eq 'official') && (&Apache::lonnet::auto_run('',$dom))) { $trail{'enrollment'} = 'Enrollment'; - } + } - my $page = 0; - my $crumb; - if (defined($action)) { - my $done = 0; - my $i=0; - if (ref($states{$action}) eq 'ARRAY') { - while ($i<@{$states{$action}} && !$done) { - if ($states{$action}[$i] eq $state) { - $page = $i; - $done = 1; - } - $i++; - } - } - for (my $i=0; $i<@{$states{$action}}; $i++) { - if ($state eq $states{$action}[$i]) { - &Apache::lonhtmlcommon::add_breadcrumb( - {text=>"$trail{$state}"}); - $crumb = &Apache::lonhtmlcommon::breadcrumbs('Course Requests','Course_Requests'); - last; - } else { - if (($state eq 'process') && ($i > 0)) { - &Apache::lonhtmlcommon::add_breadcrumb( - {href=>"javascript:backPage(document.requestcrs,'$states{$action}[0]')", - text=>"$trail{$states{$action}[$i]}"}); + my ($page,$crumb,$newinstcode,$codechk,$checkedcode,$description) = + &get_breadcrumbs($dom,$action,\$state,\%states,\%trail); + if ($action eq 'display') { + if (($dom eq $env{'request.role.domain'}) && (&Apache::lonnet::allowed('ccc',$dom))) { + my $namespace = 'courserequestqueue'; + if ($env{'form.cnum'} ne '') { + my $cnum = $env{'form.cnum'}; + my $reqkey = $cnum.'_approval'; + my $namespace = 'courserequestqueue'; + my $domconfig = &Apache::lonnet::get_domainconfiguser($dom); + my %queued = + &Apache::lonnet::get($namespace,[$reqkey],$dom,$domconfig); + if (ref($queued{$reqkey}) eq 'HASH') { + $uname = $queued{$reqkey}{'ownername'}; + $udom = $queued{$reqkey}{'ownerdom'}; + if (($udom =~ /^$match_domain$/) && ($uname =~ /^$match_username$/)) { + $result = &retrieve_settings($dom,$cnum,$udom,$uname); + } else { + $warning = &mt('Invalid username or domain for course requestor'); + } } else { - &Apache::lonhtmlcommon::add_breadcrumb( - {href=>"javascript:backPage(document.requestcrs,'$states{$action}[$i]')", - text=>"$trail{$states{$action}[$i]}"}); + $warning = &mt('No information was found for this course request.'); } + } else { + $warning = &mt('No course request ID provided.'); } + } else { + $warning = &mt('You do not have rights to view course request information.'); + } + } elsif ((defined($state)) && (defined($action))) { + if (($action eq 'view') && ($state eq 'details')) { + if ((defined($env{'form.showdom'})) && (defined($env{'form.cnum'}))) { + my $result = &retrieve_settings($env{'form.showdom'},$env{'form.cnum'}); + } + } elsif ($env{'form.crstype'} eq 'official') { + if (&Apache::lonnet::auto_run('',$dom)) { + if (($action eq 'new') && (($state eq 'enrollment') || + ($state eq 'personnel'))) { + my $checkcrosslist = 0; + for (my $i=0; $i<$env{'form.crosslisttotal'}; $i++) { + if ($env{'form.crosslist_'.$i}) { + $checkcrosslist ++; + } + } + if ($checkcrosslist) { + my %codechk; + my (@codetitles,%cat_titles,%cat_order,@code_order,$lastitem); + &Apache::lonnet::auto_possible_instcodes($dom,\@codetitles, + \%cat_titles, + \%cat_order, + \@code_order); + my $numtitles = scalar(@codetitles); + if ($numtitles) { + for (my $i=0; $i<$env{'form.crosslisttotal'}; $i++) { + if ($env{'form.crosslist_'.$i}) { + my $codecheck; + my $crosslistcode = ''; + foreach my $item (@code_order) { + $crosslistcode .= $env{'form.crosslist_'.$i.'_'.$item}; + } + if ($crosslistcode ne '') { + ($codechk{$i}, my $rest) = + &Apache::lonnet::auto_validate_instcode('',$dom,$crosslistcode); + } + unless ($codechk{$i} eq 'valid') { + $env{'form.crosslist_'.$i} = ''; + push(@invalidcrosslist,$crosslistcode); + } + } + } + } + } + } + } + } + my %elements = &form_elements($dom); + my $elementsref = {}; + if (ref($elements{$action}) eq 'HASH') { + if (ref($elements{$action}{$state}) eq 'HASH') { + $elementsref = $elements{$action}{$state}; + } + } + if (($state eq 'courseinfo') && ($env{'form.clonedom'} eq '')) { + $env{'form.clonedom'} = $dom; + } + if ($state eq 'crstype') { + $jscript = &mainmenu_javascript(); + } else { + $jscript = &Apache::lonhtmlcommon::set_form_elements($elementsref,\%stored); } - } else { - &Apache::lonhtmlcommon::add_breadcrumb( - {text=>'Pick Action'}); - $crumb = &Apache::lonhtmlcommon::breadcrumbs('Course Requests','Course_Requests'); } + if ($state eq 'personnel') { + $jscript .= "\n".&Apache::loncommon::userbrowser_javascript(); + } + + my $loaditems = &onload_action($action,$state); + my %can_request; - my $canreq = &check_can_request($dom,\%can_request); + my $canreq = &Apache::lonnet::check_can_request($dom,\%can_request); if ($action eq 'new') { if ($canreq) { if ($state eq 'crstype') { &print_main_menu($r,\%can_request,\%states,$dom,$jscript,$loaditems, $crumb); } else { - &request_administration($r,$action,$state,$page,\%states,$dom,$jscript, - $loaditems,$crumb); + &request_administration($r,$action,$state,$page,\%states,$dom, + $jscript,$loaditems,$crumb,$newinstcode, + $codechk,$checkedcode,$description, + \@invalidcrosslist); } } else { $r->print(&header('Course Requests').$crumb. @@ -245,10 +293,21 @@ sub handler { } } elsif ($action eq 'view') { if ($state eq 'crstype') { - &print_main_menu($r,\%can_request,\%states,$dom,$jscript,'',$crumb); + &print_main_menu($r,\%can_request,\%states,$dom,$jscript,$loaditems,$crumb); + } else { + &request_administration($r,$action,$state,$page,\%states,$dom,$jscript, + $loaditems,$crumb); + } + } elsif ($action eq 'display') { + if ($warning ne '') { + my $args = { only_body => 1 }; + $r->print(&header('Course Requests','','',$args).$crumb. + '
'.&mt('Review the details of the course request before submission.').'
'. - &print_review($formname,$dom,\@codetitles,\%cat_titles,\%cat_order, - \@code_order). + $r->print(''); + if ($storeresult eq 'ok') { + $r->print(''. + &mt('Modify this request').''.(' 'x4)); + } + $r->print(''.&mt('Make another request').'
'); + return; + } } my @excluded = &get_excluded_elements($dom,$states,$action,$state); if ($state eq 'personnel') { push(@excluded,'persontotal'); } + if ($state eq 'review') { + if (@disallowed > 0) { + my @items = qw(uname dom lastname firstname emailaddr hidedom role newsec); + my @currsecs = ¤t_lc_sections(); + if (@currsecs) { + push(@items,'sec'); + } + my $count = 0; + for (my $i=0; $i<$env{'form.persontotal'}; $i++) { + unless ($env{'form.person_'.$i.'_uname'} eq '') { + if (grep(/^$i$/,@disallowed)) { + foreach my $item (@items) { + $env{'form.person_'.$i.'_'.$item} = ''; + } + } else { + foreach my $item (@items) { + $env{'form.person_'.$count.'_'.$item} = $env{'form.person_'.$i.'_'.$item}; + } + } + } + $count ++; + } + $env{'form.persontotal'} = $count; + + } + } + if ($state eq 'enrollment') { + push(@excluded,'crosslisttotal'); + } $r->print(&Apache::lonhtmlcommon::echo_form_input(\@excluded).''); - &display_navbuttons($r,$formname,$prev,$navtxt{'prev'},$next,$navtxt{'next'},$state); + &display_navbuttons($r,$dom,$formname,$prev,$navtxt{'prev'},$next, + $navtxt{'next'},$state); return; } +sub get_cancreate_status { + my ($persondom,$personname,$dom) = @_; + my ($rules,$ruleorder) = + &Apache::lonnet::inst_userrules($persondom,'username'); + my $usertype = &Apache::lonuserutils::check_usertype($persondom,$personname, + $rules); + return &Apache::lonuserutils::can_create_user($dom,'requestcrs',$usertype); +} + +sub check_newuser_rules { + my ($persondom,$personname,$alerts,$rulematch,$inst_results,$curr_rules, + $got_rules) = @_; + my $allowed = 1; + my $newuser = 1; + my ($checkhash,$userchkmsg); + my $checks = { 'username' => 1 }; + $checkhash->{$personname.':'.$persondom} = { 'newuser' => $newuser }; + &Apache::loncommon::user_rule_check($checkhash,$checks,$alerts,$rulematch, + $inst_results,$curr_rules,$got_rules); + if (ref($alerts->{'username'}) eq 'HASH') { + if (ref($alerts->{'username'}{$persondom}) eq 'HASH') { + my $domdesc = + &Apache::lonnet::domain($persondom,'description'); + if ($alerts->{'username'}{$persondom}{$personname}) { + if (ref($curr_rules->{$persondom}) eq 'HASH') { + $userchkmsg = + &Apache::loncommon::instrule_disallow_msg('username', + $domdesc,1). + &Apache::loncommon::user_rule_formats($persondom, + $domdesc,$curr_rules->{$persondom}{'username'}, + 'username'); + } + $allowed = 0; + } + } + } + return ($allowed,$userchkmsg); +} + sub get_excluded_elements { my ($dom,$states,$action,$state) = @_; my @excluded = ('counter'); @@ -891,8 +1368,9 @@ sub get_excluded_elements { } sub print_enrollment_menu { - my ($formname,$instcode,$dom,$codetitles,$cat_titles,$cat_order,$code_order) =@_; - my ($sections,$autoenroll,$access_dates); + my ($formname,$instcode,$dom,$codetitles,$cat_titles,$cat_order,$code_order, + $invalidcrosslist) =@_; + my ($sections,$autoenroll,$access_dates,$output,$hasauto); my $starttime = time; my $endtime = time+(6*30*24*60*60); # 6 months from now, approx @@ -906,8 +1384,16 @@ sub print_enrollment_menu { ); if ($env{'form.crstype'} eq 'official') { if (&Apache::lonnet::auto_run('',$dom)) { - my ($section_form,$crosslist_form,$autoenroll_form); + $output = &show_invalid_crosslists($invalidcrosslist); + my ($section_form,$crosslist_form); $section_form = &inst_section_selector($dom,$instcode); + if ($section_form) { + $sections = &Apache::lonhtmlcommon::row_headline(). + ''.&mt('Username').' '.$uname_form.' | '."\n".
- ''.&mt('Domain').' '.$udom_form.' | |||||||||||
'.$usersrchlink.' | '."\n". + ''.
+ &mt('Username').': '.$uname_form.' '.$userchklink.' '."\n". + ''.&mt('Domain').': '.$udom_form.' | '.
+ '|
'.&mt('First Name').' '.$form_elems{'firstname'}.' | '."\n".
''.&mt('Last Name').' '.$form_elems{'lastname'}.' | '."\n".
''.&mt('E-mail').' '.$form_elems{'emailaddr'}.' |
'.&mt('Role').' '.$roleselector.' | '."\n".
- ''.&mt('Section(s)').' '.$sectionselector.' | '."\n".
+ '|
'.&Apache::loncommon::help_open_topic('Course_Roles').' '.&mt('Role').' '.$roleselector.' | '."\n".
+ ''.
+ &Apache::loncommon::help_open_topic('Course_Request_Rolesection').' '.&mt('LON-CAPA Section(s)').' '.$sectionselector.' | '."\n".
'
'.&mt('You may also add users later, once the course has been created, by using the "Manage course users" link, accessible from the "Main Menu".').'
'; return $output; } +sub current_lc_sections { + my @currsecs; + if ($env{'form.sectotal'}) { + for (my $i=0; $i<$env{'form.sectotal'}; $i++) { + if ($env{'form.sec_'.$i}) { + if (defined($env{'form.loncapasec_'.$i})) { + my $lcsec = $env{'form.loncapasec_'.$i}; + unless (grep(/^\Q$lcsec\E$/,@currsecs)) { + push(@currsecs,$lcsec); + } + } + } + } + } + return @currsecs; +} + sub print_request_status { my ($dom) = @_; my %statusinfo = &Apache::lonnet::dump('courserequests',$env{'user.domain'}, $env{'user.name'},'^status:'.$dom); - my ($output,$formname,%queue_by_date,%typenames); - if ($env{'form.crstype'} eq 'any') { - %typenames = &Apache::lonlocal::texthash ( - official => 'Official course', - unofficial => 'Unofficial course', - community => 'Community', - ); - } + my ($output,$formname,%queue_by_date); + my ($types,$typenames) = &Apache::loncommon::course_types(); foreach my $key (keys(%statusinfo)) { if (($statusinfo{$key} eq 'approval') || ($statusinfo{$key} eq 'pending')) { (undef,my($cdom,$cnum)) = split(':',$key); @@ -1217,7 +1775,10 @@ sub print_request_status { ''. + &mt('Not all requested personnel could be included.').'
'.&mt('Name').' | '. @@ -1589,7 +2176,7 @@ sub print_review { '
---|
'.&mt('Include?').' '. @@ -1777,7 +2378,7 @@ sub get_course_dom { } } if (($env{'user.domain'} ne '') && ($env{'user.domain'} ne 'public')) { - my ($types,$typename) = &course_types(); + my ($types,$typename) = &Apache::loncommon::course_types(); if (ref($types) eq 'ARRAY') { foreach my $type (@{$types}) { if (&Apache::lonnet::usertools_access($env{'user.name'}, @@ -1816,11 +2417,11 @@ sub get_course_dom { } sub display_navbuttons { - my ($r,$formname,$prev,$prevtext,$next,$nexttext,$state,$other,$othertext) = @_; + my ($r,$dom,$formname,$prev,$prevtext,$next,$nexttext,$state,$other,$othertext) = @_; $r->print(' '); } @@ -1927,20 +2546,27 @@ sub print_request_outcome { @{$personnel{$uname.':'.$udom}{$role}{'usec'}} = (); } else { my @currsec = &Apache::loncommon::get_env_multiple('form.person_'.$i.'_sec'); + my @allsecs; + foreach my $sec (@currsec) { + next unless ($sec =~ /\w/); + next if ($sec =~ /\W/); + next if ($sec eq 'none'); + push(@allsecs,$sec); + } my $newsec = $env{'form.person_'.$i.'_newsec'}; $newsec =~ s/^\s+//; $newsec =~s/\s+$//; my @newsecs = split(/[\s,;]+/,$newsec); foreach my $sec (@newsecs) { next if ($sec =~ /\W/); - next if ($newsec eq 'none'); + next if ($sec eq 'none'); if ($sec ne '') { - unless (grep(/^\Q$sec\E$/,@currsec)) { - push(@currsec,$sec); + unless (grep(/^\Q$sec\E$/,@allsecs)) { + push(@allsecs,$sec); } } } - @{$personnel{$uname.':'.$udom}{$role}{'usec'}} = @currsec; + @{$personnel{$uname.':'.$udom}{$role}{'usec'}} = @allsecs; } } } else { @@ -2007,7 +2633,7 @@ sub print_request_outcome { accessend => $accessend, personnel => \%personnel, }; - my @inststatuses; + my (@inststatuses,$storeresult,$creationresult); my $val = &get_processtype($dom,$crstype,\@inststatuses,\%domconfig); if ($val eq '') { if ($crstype eq 'official') { @@ -2019,6 +2645,7 @@ sub print_request_outcome { } else { $output = &mt('Unrecognized course type: [_1]',$crstype); } + $storeresult = 'notpermitted'; } else { my ($disposition,$message,$reqstatus); my %reqhash = ( @@ -2069,6 +2696,7 @@ sub print_request_outcome { if ($message) { $output .= ' '.$message.' ';
}
+ $storeresult = 'rejected';
} elsif ($disposition eq 'process') {
my %domdefs = &Apache::lonnet::get_domain_defaults($dom);
my ($logmsg,$newusermsg,$addresult,$enrollcount,$response,$keysmsg,%longroles);
@@ -2086,15 +2714,18 @@ sub print_request_outcome {
if ($result eq 'created') {
$disposition = 'created';
$reqstatus = 'created';
- $output = &mt('Your course request has been processed and the course has been created.').
- ''. - &mt('You will need to logout and log-in again to be able to select a role in the course.'); + my $role_result = &update_requestors_roles($dom,$cnum,$crstype,$details, + \%longroles); + $output = ' '.&mt('Your course request has been processed and the course has been created.').
+ ' '. &mt('You may want to review the request details and submit the request again.'). ''; + $creationresult = 'error'; } } else { my $requestid = $cnum.'_'.$disposition; @@ -2136,7 +2767,7 @@ sub print_request_outcome { } } } - my ($storeresult,$statusresult); + my ($statusresult); if ($requestkey =~ /^($match_domain)_($match_courseid)$/) { $storeresult = &Apache::lonnet::store_userdata(\%reqhash,$requestkey, 'courserequests'); @@ -2144,7 +2775,7 @@ sub print_request_outcome { my %status = ( 'status:'.$dom.':'.$cnum => $reqstatus, ); - $statusresult = &Apache::lonnet::put('courserequests',\%status); + $statusresult = &Apache::lonnet::put('courserequests',\%status); } } else { $storeresult = 'error: invalid requestkey format'; @@ -2153,7 +2784,7 @@ sub print_request_outcome { $output .= ''.&mt('An error occurred saving a record of the details of your request: [_1].',$storeresult).' '; &Apache::lonnet::logthis("Error saving course request - $requestkey for $env{'user.name'}:$env{'user.domain'} - $storeresult"); } elsif ($statusresult ne 'ok') { - $output .= ''.&mt('An error occurred saving a record of the status of your request: [_1].',$statusresult).' '; + $output .= ''.&mt('An error occurred saving a record of the status of your request: [_1].',$statusresult).' '; &Apache::lonnet::logthis("Error saving course request status for $requestkey (for $env{'user.name'}:$env{'user.domain'}) - $statusresult"); } if ($modified && $queued && $storeresult eq 'ok') { @@ -2164,6 +2795,122 @@ sub print_request_outcome { $output .= ''.&mt('An error occurred validating your request with institutional data sources: [_1].',$validationerror).''; } } + if ($creationresult ne '') { + return ($creationresult,$output); + } else { + return ($storeresult,$output); + } +} + +sub update_requestors_roles { + my ($dom,$cnum,$crstype,$details,$longroles) = @_; + my $now = time; + my ($active,$future,$numactive,$numfuture,$output); + my $owner = $env{'user.name'}.':'.$env{'user.domain'}; + if (ref($details) eq 'HASH') { + if (ref($details->{'personnel'}) eq 'HASH') { + unless (ref($details->{'personnel'}{$owner}) eq 'HASH') { + $details->{'personnel'}{$owner} = { + 'roles' => ['cc'], + 'cc' => { 'usec' => [] }, + }; + } + my @roles; + if (ref($details->{'personnel'}{$owner}{'roles'}) eq 'ARRAY') { + @roles = sort(@{$details->{'personnel'}{$owner}{'roles'}}); + unless (grep(/^cc$/,@roles)) { + push(@roles,'cc'); + } + } else { + @roles = ('cc'); + } + foreach my $role (@roles) { + my $start = $now; + my $end = '0'; + if ($role eq 'st') { + if ($details->{'accessstart'} ne '') { + $start = $details->{'accessstart'}; + } + if ($details->{'accessend'} ne '') { + $end = $details->{'accessend'}; + } + } + my @usecs; + if ($role ne 'cc') { + if (ref($details->{'personnel'}{$owner}{$role}{'usec'}) eq 'ARRAY') { + @usecs = @{$details->{'personnel'}{$owner}{$role}{'usec'}}; + } + } + if ($role eq 'st') { + if (@usecs > 1) { + my $firstsec = $usecs[0]; + @usecs = ($firstsec); + } + } + if (@usecs == 0) { + push(@usecs,''); + } + foreach my $usec (@usecs) { + my (%userroles,%newrole,%newgroups,$spec,$area); + my $area = '/'.$dom.'/'.$cnum; + my $spec = $role.'.'.$area; + if ($usec ne '') { + $spec .= '/'.$usec; + $area .= '/'.$usec; + } + if ($role =~ /^cr\//) { + &Apache::lonnet::custom_roleprivs(\%newrole,$role,$dom, + $cnum,$spec,$area); + } else { + &Apache::lonnet::standard_roleprivs(\%newrole,$role,$dom, + $spec,$cnum,$area); + } + &Apache::lonnet::set_userprivs(\%userroles,\%newrole, + \%newgroups); + $userroles{'user.role.'.$spec} = $start.'.'.$end; + &Apache::lonnet::appenv(\%userroles,[$role,'cm']); + if (($end == 0) || ($end > $now)) { + my $showrole = $role; + if ($role =~ /^cr\//) { + $showrole = &Apache::lonnet::plaintext($role,$crstype); + } elsif (ref($longroles) eq 'HASH') { + if ($longroles->{$role} ne '') { + $showrole = $longroles->{$role}; + } + } + if ($start <= $now) { + $active .= '
'; + } + if ($future) { + $output .= &mt('The following course [quant,_1,role] will become available for selection from your [_2]roles page[_3], once the default student access start date - [_4] - has been reached:',$numfuture,'','',&Apache::lonlocal::locallocaltime($details->{'accessstart'})). + '
|