# The LearningOnline Network with CAPA # User Roles Screen # # $Id: lonroles.pm,v 1.231.4.2 2009/08/14 12:41:34 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/ # ### =pod =head1 NAME Apache::lonroles - User Roles Screen =head1 SYNOPSIS Invoked by /etc/httpd/conf/srm.conf: PerlAccessHandler Apache::lonacc SetHandler perl-script PerlHandler Apache::lonroles ErrorDocument 403 /adm/login ErrorDocument 500 /adm/errorhandler =head1 OVERVIEW =head2 Choosing Roles C is a handler that allows a user to switch roles in mid-session. LON-CAPA attempts to work with "No Role Specified", the default role that a user has before selecting a role, as widely as possible, but certain handlers for example need specification which course they should act on, etc. Both in this scenario, and when the handler determines via C's C<&allowed> function that a certain action is not allowed, C is used as error handler. This allows the user to select another role which may have permission to do what they were trying to do. C can also be accessed via the B button in the Remote Control. =begin latex \begin{figure} \begin{center} \includegraphics[width=0.45\paperwidth,keepaspectratio]{Sample_Roles_Screen} \caption{\label{Sample_Roles_Screen}Sample Roles Screen} \end{center} \end{figure} =end latex =head2 Role Initialization The privileges for a user are established at login time and stored in the session environment. As a consequence, a new role does not become active till the next login. Handlers are able to query for privileges using C's C<&allowed> function. When a user first logs in, their role is the "common" role, which means that they have the sum of all of their privileges. During a session it might become necessary to choose a particular role, which as a consequence also limits the user to only the privileges in that particular role. =head1 INTRODUCTION This module enables a user to select what role he wishes to operate under (instructor, student, teaching assistant, course coordinator, etc). These roles are pre-established by the actions of upper-level users. This is part of the LearningOnline Network with CAPA project described at http://www.lon-capa.org. =head1 HANDLER SUBROUTINE This routine is called by Apache and mod_perl. =over 4 =item * Roles Initialization (yes/no) =item * Get Error Message from Environment =item * Who is this? =item * Generate Page Output =item * Choice or no choice =item * Table =item * Privileges =back =cut package Apache::lonroles; use strict; use Apache::lonnet; use Apache::lonuserstate(); use Apache::Constants qw(:common); use Apache::File(); use Apache::lonmenu; use Apache::loncommon; use Apache::lonhtmlcommon; use Apache::lonannounce; use Apache::lonlocal; use Apache::lonpageflip(); use Apache::lonnavdisplay(); use GDBM_File; use LONCAPA qw(:DEFAULT :match); use HTML::Entities; sub redirect_user { my ($r,$title,$url,$msg,$launch_nav) = @_; $msg = $title if (! defined($msg)); &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); $r->send_http_header; my $swinfo=&Apache::lonmenu::rawconfig(); my $navwindow; if ($launch_nav eq 'on') { $navwindow.=&Apache::lonnavdisplay::launch_win('now',undef,undef, ($url =~ m-^/adm/whatsnew-)); } else { $navwindow.=&Apache::lonnavmaps::close(); } my $start_page = &Apache::loncommon::start_page('Switching Role',undef, {'redirect' => [1,$url]}); my $end_page = &Apache::loncommon::end_page(); # Note to style police: # This must only replace the spaces, nothing else, or it bombs elsewhere. $url=~s/ /\%20/g; $r->print(< // $navwindow

$msg

$end_page ENDREDIR return; } sub error_page { my ($r,$error,$dest)=@_; &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); $r->send_http_header; return OK if $r->header_only; $r->print(&Apache::loncommon::start_page('Problems during Course Initialization'). ''. '

'.&mt('The following problems occurred:'). '
'. $error. '


'.&mt('Continue').''. &Apache::loncommon::end_page()); } sub handler { my $r = shift; my $now=time; my $then=$env{'user.login.time'}; my $refresh=$env{'user.refresh.time'}; if (!$refresh) { $refresh = $then; } my $envkey; my %dcroles = (); my $numdc = &check_fordc(\%dcroles,$then); &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}); # ================================================================== Roles Init if ($env{'form.selectrole'}) { my $locknum=&Apache::lonnet::get_locks(); if ($locknum) { return 409; } if ($env{'form.newrole'}) { $env{'form.'.$env{'form.newrole'}}=1; } if ($env{'request.course.id'}) { # Check if user is CC trying to select a course role if ($env{'form.switchrole'}) { if (!defined($env{'user.role.'.$env{'form.switchrole'}})) { &adhoc_course_role($then); } } my %temp=('logout_'.$env{'request.course.id'} => time); &Apache::lonnet::put('email_status',\%temp); &Apache::lonnet::delenv('user.state.'.$env{'request.course.id'}); } &Apache::lonnet::appenv({"request.course.id" => '', "request.course.fn" => '', "request.course.uri" => '', "request.course.sec" => '', "request.role" => 'cm', "request.role.adv" => $env{'user.adv'}, "request.role.domain" => $env{'user.domain'}}); # Check if user is a DC trying to enter a course or author space and needs privs to be created if ($numdc > 0) { foreach my $envkey (keys %env) { # Is this an ad-hoc CC-role? if (my ($domain,$coursenum) = ($envkey =~ m-^form\.cc\./($match_domain)/($match_courseid)$-)) { if ($dcroles{$domain}) { &Apache::lonnet::check_adhoc_privs($domain,$coursenum, $then,$refresh,$now,'cc'); } last; } # Is this an ad-hoc CA-role? if (my ($domain,$user) = ($envkey =~ m-^form\.ca\./($match_domain)/($match_username)$-)) { if (($domain eq $env{'user.domain'}) && ($user eq $env{'user.name'})) { delete($env{$envkey}); $env{'form.au./'.$domain.'/'} = 1; my ($server_status,$home) = &check_author_homeserver($user,$domain); if ($server_status eq 'switchserver') { my $trolecode = 'au./'.$domain.'/'; my $switchserver = '/adm/switchserver?otherserver='.$home.'&role='.$trolecode; $r->internal_redirect($switchserver); } last; } if (my ($castart,$caend) = ($env{'user.role.ca./'.$domain.'/'.$user} =~ /^(\d*)\.(\d*)$/)) { if (((($castart) && ($castart < $now)) || !$castart) && ((!$caend) || (($caend) && ($caend > $now)))) { my ($server_status,$home) = &check_author_homeserver($user,$domain); if ($server_status eq 'switchserver') { my $trolecode = 'ca./'.$domain.'/'.$user; my $switchserver = '/adm/switchserver?otherserver='.$home.'&role='.$trolecode; $r->internal_redirect($switchserver); } last; } } # Check if author blocked ca-access my %blocked=&Apache::lonnet::get('environment',['domcoord.author'],$domain,$user); if ($blocked{'domcoord.author'} eq 'blocked') { delete($env{$envkey}); $env{'user.error.msg'}=':::1:User '.$user.' in domain '.$domain.' blocked domain coordinator access'; last; } if ($dcroles{$domain}) { my ($server_status,$home) = &check_author_homeserver($user,$domain); if (($server_status eq 'ok') || ($server_status eq 'switchserver')) { &Apache::lonnet::check_adhoc_privs($domain,$user,$then, $refresh,$now,'ca'); if ($server_status eq 'switchserver') { my $trolecode = 'ca./'.$domain.'/'.$user; my $switchserver = '/adm/switchserver?' .'otherserver='.$home.'&role='.$trolecode; $r->internal_redirect($switchserver); } } else { delete($env{$envkey}); } } else { delete($env{$envkey}); } last; } } } foreach $envkey (keys %env) { next if ($envkey!~/^user\.role\./); my ($where,$trolecode,$role,$tstatus,$tend,$tstart); &Apache::lonnet::role_status($envkey,$then,$refresh,$now,\$role,\$where, \$trolecode,\$tstatus,\$tstart,\$tend); if ($env{'form.'.$trolecode}) { if ($tstatus eq 'is') { $where=~s/^\///; my ($cdom,$cnum,$csec)=split(/\//,$where); # check for course groups my %coursegroups = &Apache::lonnet::get_active_groups( $env{'user.domain'},$env{'user.name'},$cdom, $cnum); my $cgrps = join(':',keys(%coursegroups)); # store role if recent_role list being kept if ($env{'environment.recentroles'}) { my %frozen_roles = &Apache::lonhtmlcommon::get_recent_frozen('roles',$env{'environment.recentrolesn'}); &Apache::lonhtmlcommon::store_recent('roles', $trolecode,' ',$frozen_roles{$trolecode}); } # check for keyed access if (($role eq 'st') && ($env{'course.'.$cdom.'_'.$cnum.'.keyaccess'} eq 'yes')) { # who is key authority? my $authdom=$cdom; my $authnum=$cnum; if ($env{'course.'.$cdom.'_'.$cnum.'.keyauth'}) { ($authnum,$authdom)= split(/:/,$env{'course.'.$cdom.'_'.$cnum.'.keyauth'}); } # check with key authority unless (&Apache::lonnet::validate_access_key( $env{'environment.key.'.$cdom.'_'.$cnum}, $authdom,$authnum)) { # there is no valid key if ($env{'form.newkey'}) { # student attempts to register a new key &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); $r->send_http_header; my $swinfo=&Apache::lonmenu::rawconfig(); my $start_page=&Apache::loncommon::start_page ('Verifying Access Key to Unlock this Course'); my $end_page=&Apache::loncommon::end_page(); my $buttontext=&mt('Enter Course'); my $message=&mt('Successfully registered key'); my $assignresult= &Apache::lonnet::assign_access_key( $env{'form.newkey'}, $authdom,$authnum, $cdom,$cnum, $env{'user.domain'}, $env{'user.name'}, &mt('Assigned from [_1] at [_2] for [_3]' ,$ENV{'REMOTE_ADDR'} ,&Apache::lonlocal::locallocaltime() ,$trolecode) ); unless ($assignresult eq 'ok') { $assignresult=~s/^error\:\s*//; $message=&mt($assignresult). '
'. &mt('Logout').''; $buttontext=&mt('Re-Enter Key'); } $r->print(< //
$message
$end_page ENDENTEREDKEY return OK; } else { # print form to enter a new key &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); $r->send_http_header; my $swinfo=&Apache::lonmenu::rawconfig(); my $start_page=&Apache::loncommon::start_page ('Enter Access Key to Unlock this Course'); my $end_page=&Apache::loncommon::end_page(); $r->print(< //
$end_page ENDENTERKEY return OK; } } } &Apache::lonnet::log($env{'user.domain'}, $env{'user.name'}, $env{'user.home'}, "Role ".$trolecode); &Apache::lonnet::appenv( {'request.role' => $trolecode, 'request.role.domain' => $cdom, 'request.course.sec' => $csec, 'request.course.groups' => $cgrps}); my $tadv=0; if (($cnum) && ($role ne 'ca') && ($role ne 'aa')) { my $msg; my ($furl,$ferr)= &Apache::lonuserstate::readmap($cdom.'/'.$cnum); if (($env{'form.orgurl'}) && ($env{'form.orgurl'}!~/^\/adm\/flip/)) { my $dest=$env{'form.orgurl'}; if ($env{'form.symb'}) { if ($dest =~ /\?/) { $dest .= '&'; } else { $dest .= '?' } $dest .= 'symb='.$env{'form.symb'}; } if (&Apache::lonnet::allowed('adv') eq 'F') { $tadv=1; } &Apache::lonnet::appenv({'request.role.adv'=>$tadv}); if (($ferr) && ($tadv)) { &error_page($r,$ferr,$dest); } else { $r->internal_redirect($dest); } return OK; } else { if (!$env{'request.course.id'}) { &Apache::lonnet::appenv( {"request.course.id" => $cdom.'_'.$cnum}); $furl='/adm/roles?tryagain=1'; $msg='

' .&mt('Could not initialize [_1] at this time.', $env{'course.'.$cdom.'_'.$cnum.'.description'}) .'

' .'

'.&mt('Please try again.').'

' .'

'.$ferr.'

'; } if (&Apache::lonnet::allowed('adv') eq 'F') { $tadv=1; } &Apache::lonnet::appenv({'request.role.adv'=>$tadv}); if (($ferr) && ($tadv)) { &error_page($r,$ferr,$furl); } else { # Check to see if the user is a CC entering a course # for the first time my (undef, undef, $role, $courseid) = split(/\./, $envkey); if (substr($courseid, 0, 1) eq '/') { $courseid = substr($courseid, 1); } $courseid =~ s/\//_/; if ($role eq 'cc' && $env{'course.' . $courseid . '.course.helper.not.run'}) { $furl = "/adm/helper/course.initialization.helper"; # Send the user to the course they selected } elsif ($env{'request.course.id'}) { if ($env{'form.destinationurl'}) { my $dest = $env{'form.destinationurl'}; if ($env{'form.destsymb'} ne '') { my $esc_symb = &HTML::Entities::encode($env{'form.destsymb'},'"<>&'); $dest .= '?symb='.$esc_symb; } &redirect_user($r,&mt('Entering [_1]', $env{'course.'.$courseid.'.description'}), $dest,$msg, $env{'environment.remotenavmap'}); return OK; } if (&Apache::lonnet::allowed('whn', $env{'request.course.id'}) || &Apache::lonnet::allowed('whn', $env{'request.course.id'}.'/' .$env{'request.course.sec'}) ) { my $startpage = &courseloadpage($courseid); unless ($startpage eq 'firstres') { $msg = &mt('Entering [_1] ...', $env{'course.'.$courseid.'.description'}); &redirect_user($r,&mt('New in course'), '/adm/whatsnew?refpage=start',$msg, $env{'environment.remotenavmap'}); return OK; } } } # Are we allowed to look at the first resource? if ($furl !~ m|^/adm/|) { # Guess not ... $furl=&Apache::lonpageflip::first_accessible_resource(); } $msg = &mt('Entering [_1] ...', $env{'course.'.$courseid.'.description'}); &redirect_user($r,&mt('Entering [_1]', $env{'course.'.$courseid.'.description'}), $furl,$msg, $env{'environment.remotenavmap'}); } return OK; } } # # Send the user to the construction space they selected if ($role =~ /^(au|ca|aa)$/) { my $redirect_url = '/priv/'; if ($role eq 'au') { $redirect_url.=$env{'user.name'}; } else { $where =~ /\/(.*)$/; $redirect_url .= $1; } $redirect_url .= '/'; &redirect_user($r,&mt('Entering Construction Space'), $redirect_url); return OK; } if ($role eq 'dc') { my $redirect_url = '/adm/menu/'; &redirect_user($r,&mt('Loading Domain Coordinator Menu'), $redirect_url); return OK; } if ($role eq 'sc') { my $redirect_url = '/adm/grades?command=scantronupload'; &redirect_user($r,&mt('Loading Data Upload Page'), $redirect_url); return OK; } } } } } # =============================================================== No Roles Init &Apache::loncommon::content_type($r,'text/html'); &Apache::loncommon::no_cache($r); $r->send_http_header; return OK if $r->header_only; my $crumbtext = 'User Roles'; my $pagetitle = 'My Roles'; my $recent = &mt('Recent Roles'); my $show_course=&Apache::loncommon::show_course(); if ($show_course) { $crumbtext = 'Courses'; $pagetitle = 'My Courses'; $recent = &mt('Recent Courses'); } my $brcrum =[{href=>"/adm/roles",text=>$crumbtext}]; my $swinfo=&Apache::lonmenu::rawconfig(); my $start_page=&Apache::loncommon::start_page($pagetitle,undef,{bread_crumbs=>$brcrum}); my $standby=&mt('Role selected. Please stand by.'); $standby=~s/\n/\\n/g; my $noscript=''.&mt('Use of LON-CAPA requires Javascript to be enabled in your web browser.').'
'.&mt('As this is not the case, most functionality in the system will be unavailable.').'

'; $r->print(< ENDHEADER # ------------------------------------------ Get Error Message from Environment my ($fn,$priv,$nochoose,$error,$msg)=split(/:/,$env{'user.error.msg'}); if ($env{'user.error.msg'}) { $r->log_reason( "$msg for $env{'user.name'} domain $env{'user.domain'} access $priv",$fn); } # ------------------------------------------------- Can this user re-init, etc? my $advanced=$env{'user.adv'}; &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['tryagain']); my $tryagain=$env{'form.tryagain'}; my $reinit=$env{'user.reinit'}; delete $env{'user.reinit'}; # -------------------------------------------------------- Generate Page Output # --------------------------------------------------------------- Error Header? if ($error) { $r->print("

".&mt('LON-CAPA Access Control')."

"); $r->print("
");
	if ($priv ne '') {
            $r->print(&mt('Access  : ').&Apache::lonnet::plaintext($priv)."\n");
	}
	if ($fn ne '') {
            $r->print(&mt('Resource: ').&Apache::lonenc::check_encrypt($fn)."\n");
	}
	if ($msg ne '') {
            $r->print(&mt('Action  : ').$msg."\n");
	}
	$r->print("

"); my $url=$fn; my $last; if (tie(my %hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', &GDBM_READER(),0640)) { $last=$hash{'last_known'}; untie(%hash); } if ($last) { $fn.='?symb='.&escape($last); } &Apache::londocs::changewarning($r,undef,'You have modified your course recently, [_1] may fix this access problem.', &Apache::lonenc::check_encrypt($fn)); } else { if ($env{'user.error.msg'}) { if ($reinit) { $r->print( '

'. &mt('As your session file for the course has expired, you will need to re-select the course.').'

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

'. &mt('You need to choose another user role or enter a specific course for this function').'

'); } } } # -------------------------------------------------------- Choice or no choice? if ($nochoose) { $r->print("

".&mt('Sorry ...')."

\n". &mt('This action is currently not authorized.').''. &Apache::loncommon::end_page()); return OK; } else { if (($ENV{'REDIRECT_QUERY_STRING'}) && ($fn)) { $fn.='?'.$ENV{'REDIRECT_QUERY_STRING'}; } $r->print('
'); $r->print(''); $r->print(''); $r->print(''); } my (%roletext,%sortrole,%roleclass,%futureroles,%timezones); my ($countactive,$countfuture,$inrole,$possiblerole) = &gather_roles($then,$refresh,$now,$reinit,$nochoose,\%roletext,\%sortrole,\%roleclass, \%futureroles,\%timezones); $refresh = $now; &Apache::lonnet::appenv({'user.refresh.time' => $refresh}); if ($env{'user.adv'}) { $r->print( '

'); } else { if ($countactive > 0) { &queued_selfenrollment($r); my $domdesc = &Apache::lonnet::domain($env{'user.domain'},'description'); my $esc_dom = &HTML::Entities::encode($env{'user.domain'},'"<>&'); $r->print('

'.&mt('[_1]Visit the [_2]Course Catalog[_3] to view all [_4] LON-CAPA courses.','','','',$domdesc).'
'.&mt('If a course is [_1]not[_2] in your list of current courses below, you may be able to enroll if self-enrollment is permitted.','','').'

'); } } # No active roles if ($countactive==0) { if ($inrole) { $r->print('

'.&mt('Currently no additional roles or courses').'

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

'.&mt('Currently no active roles or courses').'

'); } &findcourse_advice($r); $r->print('
'); if ($countfuture) { $r->print(&mt('The following [quant,_1,role,roles] will become active in the future:',$countfuture)); my $doheaders = &roletable_headers($r,\%roleclass,\%sortrole, $nochoose); &print_rolerows($r,$doheaders,\%roleclass,\%sortrole,\%dcroles, \%roletext); my $tremark=''; my $tbg; if ($env{'request.role'} eq 'cm') { $tbg="LC_roles_selected"; $tremark=&mt('Currently selected.').' '; } else { $tbg="LC_roles_is"; } $r->print(&Apache::loncommon::start_data_table_row() .' ' .'' .&mt('No role specified') .'' .''.$tremark.' ' .&Apache::loncommon::end_data_table_row() ); $r->print(&Apache::loncommon::end_data_table()); } $r->print(&Apache::loncommon::end_page()); return OK; } # ----------------------------------------------------------------------- Table unless ((!&Apache::loncommon::show_course()) || ($nochoose) || ($countactive==1)) { $r->print("

".&mt('Select a Course to Enter')."

\n"); } if ($env{'form.destinationurl'}) { $r->print(''); if ($env{'form.destsymb'} ne '') { $r->print(''); } } my $doheaders = &roletable_headers($r,\%roleclass,\%sortrole,$nochoose); if ($env{'environment.recentroles'}) { my %recent_roles = &Apache::lonhtmlcommon::get_recent('roles',$env{'environment.recentrolesn'}); my $output=''; foreach (sort(keys(%recent_roles))) { if (ref($roletext{'user.role.'.$_}) eq 'ARRAY') { $output.= &Apache::loncommon::start_data_table_row(). $roletext{'user.role.'.$_}->[0]. &Apache::loncommon::end_data_table_row(). &Apache::loncommon::continue_data_table_row(). $roletext{'user.role.'.$_}->[1]. &Apache::loncommon::end_data_table_row(); if ($_ =~ m-dc\./($match_domain)/- && $dcroles{$1}) { $output .= &adhoc_roles_row($1,'recent'); } } elsif ($numdc > 0) { unless ($_ =~/^error\:/) { $output.=&display_cc_role('user.role.'.$_); } } } if ($output) { $r->print(&Apache::loncommon::start_data_table_empty_row() .'' .$recent .'' .&Apache::loncommon::end_data_table_empty_row() ); $r->print($output); $doheaders ++; } } if ($numdc > 0) { $r->print(&coursepick_jscript()); $r->print(&Apache::loncommon::coursebrowser_javascript(). &Apache::loncommon::authorbrowser_javascript()); } &print_rolerows($r,$doheaders,\%roleclass,\%sortrole,\%dcroles,\%roletext); if ($countactive > 1) { my $tremark=''; my $tbg; if ($env{'request.role'} eq 'cm') { $tbg="LC_roles_selected"; $tremark=&mt('Currently selected.').' '; } else { $tbg="LC_roles_is"; } $r->print(&Apache::loncommon::start_data_table_row()); unless ($nochoose) { if ($env{'request.role'} ne 'cm') { $r->print(''); } else { $r->print(' '); } } $r->print('' .&mt('No role specified') .'' .''.$tremark.' ' .&Apache::loncommon::end_data_table_row() ); } $r->print(&Apache::loncommon::end_data_table()); unless ($nochoose) { $r->print("\n"); } # ------------------------------------------------------------ Privileges Info if (($advanced) && (($env{'user.error.msg'}) || ($error))) { $r->print('

'.&mt('Current Privileges').'

'); $r->print(&privileges_info()); } $r->print(&Apache::lonnet::getannounce()); if ($advanced) { my $esc_dom = &HTML::Entities::encode($env{'user.domain'},'"<>&'); $r->print('

' .&mt('This LON-CAPA server is version [_1]',$r->dir_config('lonVersion')) .'
' .''.&mt('Logout').'  ' .'' .&mt('Course Catalog') .'

'); } $r->print(&Apache::loncommon::end_page()); return OK; } sub gather_roles { my ($then,$refresh,$now,$reinit,$nochoose,$roletext,$sortrole,$roleclass,$futureroles,$timezones) = @_; my ($countactive,$countfuture,$inrole,$possiblerole) = (0,0,0,''); my $advanced = $env{'user.adv'}; my $tryagain = $env{'form.tryagain'}; foreach my $envkey (sort(keys(%env))) { my $button = 1; my $switchserver=''; my ($role_text,$role_text_end,$sortkey); if ($envkey=~/^user\.role\./) { my ($role,$where,$trolecode,$tstart,$tend,$tremark,$tstatus,$tpstart,$tpend); &Apache::lonnet::role_status($envkey,$then,$refresh,$now,\$role,\$where, \$trolecode,\$tstatus,\$tstart,\$tend); next if (!defined($role) || $role eq '' || $role =~ /^gr/); my $timezone = &role_timezone($where,$timezones); $tremark=''; $tpstart=' '; $tpend=' '; if ($tstart) { $tpstart=&Apache::lonlocal::locallocaltime($tstart,$timezone); } if ($tend) { $tpend=&Apache::lonlocal::locallocaltime($tend,$timezone); } if ($env{'request.role'} eq $trolecode) { $tstatus='selected'; } my $tbg; if (($tstatus eq 'is') || ($tstatus eq 'selected') || ($tstatus eq 'future') || ($env{'form.showall'})) { if ($tstatus eq 'is') { $tbg='LC_roles_is'; $possiblerole=$trolecode; $countactive++; } elsif ($tstatus eq 'future') { $tbg='LC_roles_future'; $button=0; $futureroles->{$trolecode} = $tstart.':'.$tend; $countfuture ++; } elsif ($tstatus eq 'expired') { $tbg='LC_roles_expired'; $button=0; } elsif ($tstatus eq 'will_not') { $tbg='LC_roles_will_not'; $tremark.=&mt('Expired after logout.').' '; } elsif ($tstatus eq 'selected') { $tbg='LC_roles_selected'; $inrole=1; $countactive++; $tremark.=&mt('Currently selected.').' '; } my $trole; if ($role =~ /^cr\//) { my ($rdummy,$rdomain,$rauthor,$rrole)=split(/\//,$role); if ($tremark) { $tremark.='
'; } $tremark.=&mt('Defined by [_1] at [_2].',$rauthor,$rdomain); } $trole=Apache::lonnet::plaintext($role); my $ttype; my $twhere; my ($tdom,$trest,$tsection)= split(/\//,Apache::lonnet::declutter($where)); # First, Co-Authorship roles if (($role eq 'ca') || ($role eq 'aa')) { my $home = &Apache::lonnet::homeserver($trest,$tdom); my $allowed=0; my @ids=&Apache::lonnet::current_machine_ids(); foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } } if (!$allowed) { $button=0; $switchserver='otherserver='.$home.'&role='.$trolecode; } #next if ($home eq 'no_host'); $home = &Apache::lonnet::hostname($home); $ttype='Construction Space'; $twhere=&mt('User').': '.$trest.'
'.&mt('Domain'). ': '.$tdom.'
'. ' '.&mt('Server').': '.$home; $env{'course.'.$tdom.'_'.$trest.'.description'}='ca'; $tremark.=&Apache::lonhtmlcommon::authorbombs('/res/'.$tdom.'/'.$trest.'/'); $sortkey=$role."$trest:$tdom"; } elsif ($role eq 'au') { # Authors my $home = &Apache::lonnet::homeserver ($env{'user.name'},$env{'user.domain'}); my $allowed=0; my @ids=&Apache::lonnet::current_machine_ids(); foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } } if (!$allowed) { $button=0; $switchserver='otherserver='.$home.'&role='.$trolecode; } #next if ($home eq 'no_host'); $home = &Apache::lonnet::hostname($home); $ttype='Construction Space'; $twhere=&mt('Domain').': '.$tdom.'
'.&mt('Server'). ': '.$home; $env{'course.'.$tdom.'_'.$trest.'.description'}='ca'; $tremark.=&Apache::lonhtmlcommon::authorbombs('/res/'.$tdom.'/'.$env{'user.name'}.'/'); $sortkey=$role; } elsif ($trest) { my $tcourseid=$tdom.'_'.$trest; $ttype = &Apache::loncommon::course_type($tcourseid); $trole = &Apache::lonnet::plaintext($role,$ttype); if ($env{'course.'.$tcourseid.'.description'}) { $twhere=$env{'course.'.$tcourseid.'.description'}; $sortkey=$role."\0".$tdom."\0".$twhere."\0".$envkey; unless ($twhere eq &mt('Currently not available')) { $twhere.=' '. &Apache::loncommon::syllabuswrapper(&mt('Syllabus'),$trest,$tdom). ''; } } else { my %newhash=&Apache::lonnet::coursedescription($tcourseid); if (%newhash) { $sortkey=$role."\0".$tdom."\0".$newhash{'description'}. "\0".$envkey; $twhere=$newhash{'description'}. ' '. &Apache::loncommon::syllabuswrapper(&mt('Syllabus'),$trest,$tdom). ''; $ttype = $newhash{'type'}; $trole = &Apache::lonnet::plaintext($role,$ttype); } else { $twhere=&mt('Currently not available'); $env{'course.'.$tcourseid.'.description'}=$twhere; $sortkey=$role."\0".$tdom."\0".$twhere."\0".$envkey; $ttype = 'Unavailable'; } } if ($tsection) { $twhere.='
'.&mt('Section').': '.$tsection; } if ($role ne 'st') { $twhere.="
".&mt('Domain').":".$tdom; } } elsif ($tdom) { $ttype='Domain'; $twhere=$tdom; $sortkey=$role.$twhere; } else { $ttype='System'; $twhere=&mt('system wide'); $sortkey=$role.$twhere; } ($role_text,$role_text_end) = &build_roletext($trolecode,$tdom,$trest,$tstatus,$tryagain, $advanced,$tremark,$tbg,$trole,$twhere,$tpstart, $tpend,$nochoose,$button,$switchserver,$reinit); $roletext->{$envkey}=[$role_text,$role_text_end]; if (!$sortkey) {$sortkey=$twhere."\0".$envkey;} $sortrole->{$sortkey}=$envkey; $roleclass->{$envkey}=$ttype; } } } return ($countactive,$countfuture,$inrole,$possiblerole); } sub role_timezone { my ($where,$timezones) = @_; my $timezone; if (ref($timezones) eq 'HASH') { if ($where =~ m{^/($match_domain)/($match_courseid)}) { my $cdom = $1; my $cnum = $2; if ($cdom && $cnum) { if (!exists($timezones->{$cdom.'_'.$cnum})) { my %timehash = &Apache::lonnet::get('environment',['timezone'],$cdom,$cnum); if ($timehash{'timezone'} eq '') { if (!exists($timezones->{$cdom})) { my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom); if ($domdefaults{'timezone_def'} eq '') { $timezones->{$cdom} = 'local'; } else { $timezones->{$cdom} = $domdefaults{'timezone_def'}; } } $timezones->{$cdom.'_'.$cnum} = $timezones->{$cdom}; } else { $timezones->{$cdom.'_'.$cnum} = &Apache::lonlocal::gettimezone($timehash{'timezone'}); } } $timezone = $timezones->{$cdom.'_'.$cnum}; } } else { my ($tdom) = ($where =~ m{^/($match_domain)}); if ($tdom) { if (!exists($timezones->{$tdom})) { my %domdefaults = &Apache::lonnet::get_domain_defaults($tdom); if ($domdefaults{'timezone_def'} eq '') { $timezones->{$tdom} = 'local'; } else { $timezones->{$tdom} = $domdefaults{'timezone_def'}; } } $timezone = $timezones->{$tdom}; } } if ($timezone eq 'local') { $timezone = undef; } } return $timezone; } sub roletable_headers { my ($r,$roleclass,$sortrole,$nochoose) = @_; my $doheaders; if ((ref($sortrole) eq 'HASH') && (ref($roleclass) eq 'HASH')) { $r->print('
' .&Apache::loncommon::start_data_table() .&Apache::loncommon::start_data_table_header_row() ); if (!$nochoose) { $r->print(' '); } $r->print(''.&mt('User Role').'' .''.&mt('Extent').'' .''.&mt('Start').'' .''.&mt('End').'' .&Apache::loncommon::end_data_table_header_row() ); $doheaders=-1; my @roletypes = &roletypes(); foreach my $type (@roletypes) { my $haverole=0; foreach my $which (sort {uc($a) cmp uc($b)} (keys(%{$sortrole}))) { if ($roleclass->{$sortrole->{$which}} =~ /^\Q$type\E/) { $haverole=1; } } if ($haverole) { $doheaders++; } } } return $doheaders; } sub roletypes { my @types = ('Domain','Construction Space','Course','Unavailable','System'); return @types; } sub print_rolerows { my ($r,$doheaders,$roleclass,$sortrole,$dcroles,$roletext) = @_; if ((ref($roleclass) eq 'HASH') && (ref($sortrole) eq 'HASH')) { my @types = &roletypes(); foreach my $type (@types) { my $output; foreach my $which (sort {uc($a) cmp uc($b)} (keys(%{$sortrole}))) { if ($roleclass->{$sortrole->{$which}} =~ /^\Q$type\E/) { if (ref($roletext) eq 'HASH') { if (ref($roletext->{$sortrole->{$which}}) eq 'ARRAY') { $output.= &Apache::loncommon::start_data_table_row(). $roletext->{$sortrole->{$which}}->[0]. &Apache::loncommon::end_data_table_row(). &Apache::loncommon::continue_data_table_row(). $roletext->{$sortrole->{$which}}->[1]. &Apache::loncommon::end_data_table_row(); } if ($sortrole->{$which} =~ m-dc\./($match_domain)/-) { if (ref($dcroles) eq 'HASH') { if ($dcroles->{$1}) { $output .= &adhoc_roles_row($1,''); } } } } } } if ($output) { if ($doheaders > 0) { $r->print(&Apache::loncommon::start_data_table_empty_row() .'' .&mt($type) .'' .&Apache::loncommon::end_data_table_empty_row() ); } $r->print($output); } } } } sub findcourse_advice { my ($r) = @_; my $domdesc = &Apache::lonnet::domain($env{'user.domain'},'description'); my $esc_dom = &HTML::Entities::encode($env{'user.domain'},'"<>&'); if (&Apache::lonnet::auto_run(undef,$env{'user.domain'})) { $r->print(&mt('If you were expecting to see an active role listed for a particular course in the [_1] domain, it may be missing for one of the following reasons:',$domdesc).'
  • '.&mt('The course has yet to be created.').'
  • '.&mt('Automatic enrollment of registered students has not been enabled for the course.').'
  • '.&mt('You are in a section of course for which automatic enrollment in the corresponding LON-CAPA course is not active.').'
  • '.&mt('The start date for automated enrollment has yet to be reached.').'
  • '.&mt('You registered for the course recently and there is a time lag between the time you register, and the time this information becomes available for the update of LON-CAPA course rosters.').'
'); } else { $r->print(&mt('If you were expecting to see an active role listed for a particular course, that course may not have been created yet.').'
'); } $r->print('

'.&mt('The [_1]Course Catalog[_2] provides information about all [_3] classes for which LON-CAPA courses have been created.','','',$domdesc).'
'); $r->print(&mt('You can search the course catalog for courses which permit self-enrollment, if you would like to enroll in a course.').'

'); &queued_selfenrollment($r); return; } sub queued_selfenrollment { my ($r) = @_; my %selfenrollrequests = &Apache::lonnet::dump('selfenrollrequests'); my %reqs_by_date; foreach my $item (keys(%selfenrollrequests)) { if (ref($selfenrollrequests{$item}) eq 'HASH') { if ($selfenrollrequests{$item}{'status'} eq 'request') { if ($selfenrollrequests{$item}{'timestamp'}) { push(@{$reqs_by_date{$selfenrollrequests{$item}{'timestamp'}}},$item); } } } } if (keys(%reqs_by_date)) { my $rolename = &Apache::lonnet::plaintext('st'); $r->print(''.&mt('Enrollment requests pending Course Coordinator approval').'
'. &Apache::loncommon::start_data_table(). &Apache::loncommon::start_data_table_header_row(). ''.&mt('Date requested').''.&mt('Course title').''. ''.&mt('User role').''.&mt('Section').''. &Apache::loncommon::end_data_table_header_row()); my @sorted = sort { $a <=> $b } (keys(%reqs_by_date)); foreach my $item (@sorted) { if (ref($reqs_by_date{$item}) eq 'ARRAY') { foreach my $crs (@{$reqs_by_date{$item}}) { my %courseinfo = &Apache::lonnet::coursedescription($crs); my $usec = $selfenrollrequests{$crs}{'section'}; if ($usec eq '') { $usec = &mt('No section'); } $r->print(&Apache::loncommon::start_data_table_row(). ''.&Apache::lonlocal::locallocaltime($item).''. ''.$courseinfo{'description'}.''. ''.$rolename.''.$usec.''. &Apache::loncommon::end_data_table_row()); } } } $r->print(&Apache::loncommon::end_data_table()); } return; } sub privileges_info { my ($which) = @_; my $output; $which ||= $env{'request.role'}; foreach my $envkey (sort(keys(%env))) { next if ($envkey!~/^user\.priv\.\Q$which\E\.(.*)/); my $where=$1; my $ttype; my $twhere; my (undef,$tdom,$trest,$tsec)=split(m{/},$where); if ($trest) { if ($env{'course.'.$tdom.'_'.$trest.'.description'} eq 'ca') { $ttype='Construction Space'; $twhere='User: '.$trest.', Domain: '.$tdom; } else { $ttype= &Apache::loncommon::course_type($tdom.'_'.$trest); $twhere=$env{'course.'.$tdom.'_'.$trest.'.description'}; if ($tsec) { my $sec_type = 'Section'; if (exists($env{"user.role.gr.$where"})) { $sec_type = 'Group'; } $twhere.=' ('.$sec_type.': '.$tsec.')'; } } } elsif ($tdom) { $ttype='Domain'; $twhere=$tdom; } else { $ttype='System'; $twhere='/'; } $output .= "\n

".&mt($ttype).': '.$twhere.'

'."\n
    "; foreach my $priv (sort(split(/:/,$env{$envkey}))) { next if (!$priv); my ($prv,$restr)=split(/\&/,$priv); my $trestr=''; if ($restr ne 'F') { $trestr.=' ('. join(', ', map { &Apache::lonnet::plaintext($_) } (split('',$restr))).') '; } $output .= "\n\t". '
  • '.&Apache::lonnet::plaintext($prv).$trestr.'
  • '; } $output .= "\n".'
'; } return $output; } sub build_roletext { my ($trolecode,$tdom,$trest,$tstatus,$tryagain,$advanced,$tremark,$tbg,$trole,$twhere,$tpstart,$tpend,$nochoose,$button,$switchserver,$reinit) = @_; my ($roletext,$roletext_end); my $is_dc=($trolecode =~ m/^dc\./); my $rowspan=($is_dc) ? '' : ' rowspan="2" '; unless ($nochoose) { my $buttonname=$trolecode; $buttonname=~s/\W//g; if (!$button) { if ($switchserver) { $roletext.='' .'' .&mt('Switch Server') .''; } else { $roletext.=(' '); } } elsif ($tstatus eq 'is') { $roletext.=''. ''; } elsif ($tryagain) { $roletext.= ''. ''; } elsif ($advanced) { $roletext.= ''. ''; } elsif ($reinit) { $roletext.= ''. ''; } else { $roletext.= ''. ''; } } if ($trolecode !~ m/^(dc|ca|au|aa)\./) { $tremark.=&Apache::lonannounce::showday(time,1, &Apache::lonannounce::readcalendar($tdom.'_'.$trest)); } $roletext.=''.$trole.'' .''.$twhere.'' .''.$tpstart.'' .''.$tpend.''; if (!$is_dc) { $roletext_end = ''. $tremark.' '. ''; } return ($roletext,$roletext_end); } sub check_needs_switchserver { my ($possiblerole) = @_; my $needs_switchserver; my ($role,$where) = split(/\./,$possiblerole,2); my (undef,$tdom,$twho) = split(/\//,$where); my ($server_status,$home); if (($role eq 'ca') || ($role eq 'aa')) { ($server_status,$home) = &check_author_homeserver($twho,$tdom); } else { ($server_status,$home) = &check_author_homeserver($env{'user.name'}, $env{'user.domain'}); } if ($server_status eq 'switchserver') { $needs_switchserver = 1; } return $needs_switchserver; } sub check_author_homeserver { my ($uname,$udom)=@_; if (($uname eq '') || ($udom eq '')) { return ('fail',''); } my $home = &Apache::lonnet::homeserver($uname,$udom); if (&Apache::lonnet::host_domain($home) ne $udom) { return ('fail',$home); } my @ids=&Apache::lonnet::current_machine_ids(); if (grep(/^\Q$home\E$/,@ids)) { return ('ok',$home); } else { return ('switchserver',$home); } } sub check_fordc { my ($dcroles,$then) = @_; my $numdc = 0; if ($env{'user.adv'}) { foreach my $envkey (sort keys %env) { if ($envkey=~/^user\.role\.dc\.\/($match_domain)\/$/) { my $dcdom = $1; my $livedc = 1; my ($tstart,$tend)=split(/\./,$env{$envkey}); if ($tstart && $tstart>$then) { $livedc = 0; } if ($tend && $tend <$then) { $livedc = 0; } if ($livedc) { $$dcroles{$dcdom} = $envkey; $numdc++; } } } } return $numdc; } sub adhoc_course_role { my ($then) = @_; my ($cdom,$cnum); $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; if (&check_forcc($cdom,$cnum,$then)) { my $setprivs; if (!defined($env{'user.role.'.$env{'form.switchrole'}})) { $setprivs = 1; } else { my ($start,$end) = split(/\./,$env{'user.role.'.$env{'form.switchrole'}}); if (($start && ($start>$then || $start == -1)) || ($end && $end<$then)) { $setprivs = 1; } } if ($setprivs) { if ($env{'form.switchrole'} =~ m-^(in|ta|ep|ad|st|cr)([\w/]*)\./\Q$cdom\E/\Q$cnum\E/?(\w*)$-) { my $role = $1; my $custom_role = $2; my $usec = $3; if ($role eq 'cr') { if ($custom_role =~ m-^/$match_domain/$match_username/\w+$-) { $role .= $custom_role; } else { return; } } my (%userroles,%newrole,%newgroups,%group_privs); my %cgroups = &Apache::lonnet::get_active_groups($env{'user.domain'}, $env{'user.name'},$cdom,$cnum); foreach my $group (keys(%cgroups)) { $group_privs{$group} = $env{'user.priv.cc./'.$cdom.'/'.$cnum.'./'.$cdom.'/'.$cnum.'/'.$group}; } $newgroups{'/'.$cdom.'/'.$cnum} = \%group_privs; my $area = '/'.$cdom.'/'.$cnum; my $spec = $role.'.'.$area; if ($usec ne '') { $spec .= '/'.$usec; $area .= '/'.$usec; } &Apache::lonnet::standard_roleprivs(\%newrole,$role,$cdom,$spec,$cnum,$area); &Apache::lonnet::set_userprivs(\%userroles,\%newrole,\%newgroups); my $adhocstart = $then-1; $userroles{'user.role.'.$spec} = $adhocstart.'.'; &Apache::lonnet::appenv(\%userroles,[$role,'cm']); } } } return; } sub check_forcc { my ($cdom,$cnum,$then) = @_; my $is_cc; if ($cdom ne '' && $cnum ne '') { if (&Apache::lonnet::is_course($cdom,$cnum)) { my $envkey = 'user.role.cc./'.$cdom.'/'.$cnum; if (defined($env{$envkey})) { $is_cc = 1; my ($tstart,$tend)=split(/\./,$env{$envkey}); if ($tstart && $tstart>$then) { $is_cc = 0; } if ($tend && $tend <$then) { $is_cc = 0; } } } } return $is_cc; } sub courselink { my ($dcdom,$rowtype) = @_; my $courseform=&Apache::loncommon::selectcourse_link ('rolechoice','dccourse'.$rowtype.'_'.$dcdom, 'dcdomain'.$rowtype.'_'.$dcdom,'coursedesc'.$rowtype.'_'. $dcdom,$dcdom,undef); my $hiddenitems = ''. ''. ''. ''; return $courseform.$hiddenitems; } sub coursepick_jscript { my %lt = &Apache::lonlocal::texthash( plsu => "Please use the 'Select Course' link to open a separate pick course window where you may select the course you wish to enter.", youc => 'You can only use this screen to select courses in the current domain.', ); my $verify_script = <<"END"; END return $verify_script; } sub coauthorlink { my ($dcdom,$rowtype) = @_; my $coauthorform=&Apache::loncommon::selectauthor_link('rolechoice',$dcdom); my $hiddenitems = ''; return $coauthorform.$hiddenitems; } sub display_cc_role { my $rolekey = shift; my ($roletext,$roletext_end); my $advanced = $env{'user.adv'}; my $tryagain = $env{'form.tryagain'}; unless ($rolekey =~/^error\:/) { if ($rolekey =~ m-^user\.role.cc\./($match_domain)/($match_courseid)$-) { my $tcourseid = $1.'_'.$2; my $trolecode = 'cc./'.$1.'/'.$2; my $twhere; my $ttype; my $tbg='LC_roles_is'; my %newhash=&Apache::lonnet::coursedescription($tcourseid); if (%newhash) { $twhere=$newhash{'description'}. ' '. &Apache::loncommon::syllabuswrapper(&mt('Syllabus'),$2,$1). ''; $ttype = $newhash{'type'}; } else { $twhere=&mt('Currently not available'); $env{'course.'.$tcourseid.'.description'}=$twhere; } my $trole = &Apache::lonnet::plaintext('cc',$ttype); $twhere.="
".&mt('Domain').":".$1; ($roletext,$roletext_end) = &build_roletext($trolecode,$1,$2,'is',$tryagain,$advanced,'',$tbg,$trole,$twhere,'','','',1,''); } } return ($roletext,$roletext_end); } sub adhoc_roles_row { my ($dcdom,$rowtype) = @_; my $output = &Apache::loncommon::continue_data_table_row() .' ' .&mt('[_1]Ad hoc[_2] roles in domain [_3] --' ,'','',$dcdom) .' '; my $selectcclink = &courselink($dcdom,$rowtype); my $ccrole = &Apache::lonnet::plaintext('cc'); my $carole = &Apache::lonnet::plaintext('ca'); my $selectcalink = &coauthorlink($dcdom,$rowtype); $output.=$ccrole.': '.$selectcclink .' | '.$carole.': '.$selectcalink .&Apache::loncommon::end_data_table_row(); return $output; } sub recent_filename { my $area=shift; return 'nohist_recent_'.&escape($area); } sub courseloadpage { my ($courseid) = @_; my $startpage; my %entry_settings = &Apache::lonnet::get('nohist_whatsnew', [$courseid.':courseinit']); my ($tmp) = %entry_settings; unless ($tmp =~ /^error: 2 /) { $startpage = $entry_settings{$courseid.':courseinit'}; } if ($startpage eq '') { if (exists($env{'environment.course_init_display'})) { $startpage = $env{'environment.course_init_display'}; } } return $startpage; } 1; __END__ =head1 NAME Apache::lonroles - User Roles Screen =head1 SYNOPSIS Invoked by /etc/httpd/conf/srm.conf: PerlAccessHandler Apache::lonacc SetHandler perl-script PerlHandler Apache::lonroles ErrorDocument 403 /adm/login ErrorDocument 500 /adm/errorhandler =head1 OVERVIEW =head2 Choosing Roles C is a handler that allows a user to switch roles in mid-session. LON-CAPA attempts to work with "No Role Specified", the default role that a user has before selecting a role, as widely as possible, but certain handlers for example need specification which course they should act on, etc. Both in this scenario, and when the handler determines via C's C<&allowed> function that a certain action is not allowed, C is used as error handler. This allows the user to select another role which may have permission to do what they were trying to do. C can also be accessed via the B button in the Remote Control. =begin latex \begin{figure} \begin{center} \includegraphics[width=0.45\paperwidth,keepaspectratio]{Sample_Roles_Screen} \caption{\label{Sample_Roles_Screen}Sample Roles Screen} \end{center} \end{figure} =end latex =head2 Role Initialization The privileges for a user are established at login time and stored in the session environment. As a consequence, a new role does not become active till the next login. Handlers are able to query for privileges using C's C<&allowed> function. When a user first logs in, their role is the "common" role, which means that they have the sum of all of their privileges. During a session it might become necessary to choose a particular role, which as a consequence also limits the user to only the privileges in that particular role. =head1 INTRODUCTION This module enables a user to select what role he wishes to operate under (instructor, student, teaching assistant, course coordinator, etc). These roles are pre-established by the actions of upper-level users. This is part of the LearningOnline Network with CAPA project described at http://www.lon-capa.org. =head1 HANDLER SUBROUTINE This routine is called by Apache and mod_perl. =over 4 =item * Roles Initialization (yes/no) =item * Get Error Message from Environment =item * Who is this? =item * Generate Page Output =item * Choice or no choice =item * Table =item * Privileges =back =cut