[LON-CAPA-cvs] cvs: loncom / loncapa_apache.conf lond /auth lonauth.pm lonroles.pm /automation batchcreatecourse.pm /enrollment localenroll.pm /homework inputtags.pm structuretags.pm /interface domainprefs.pm loncommon.pm loncreateuser.pm londocs.pm lonhtmlcommon.pm lonmenu.pm lonmodifycourse.pm lonnavdisplay.pm lonpickcourse.pm lonplacementtest.pm lonquickgrades.pm lonrequestcourse.pm lonuserutils.pm /lonnet/perl lonnet.pm /misc releaseslist.xml doc/loncapafiles loncapafiles.lpml

raeburn raeburn at source.lon-capa.org
Sat Apr 2 00:31:35 EDT 2016


raeburn		Sat Apr  2 04:31:35 2016 EDT

  Added files:                 
    /loncom/interface	lonplacementtest.pm 

  Modified files:              
    /loncom/interface	domainprefs.pm loncommon.pm loncreateuser.pm 
                     	londocs.pm lonhtmlcommon.pm lonmenu.pm 
                     	lonmodifycourse.pm lonnavdisplay.pm 
                     	lonpickcourse.pm lonquickgrades.pm 
                     	lonrequestcourse.pm lonuserutils.pm 
    /loncom	lond loncapa_apache.conf 
    /loncom/homework	structuretags.pm inputtags.pm 
    /loncom/automation	batchcreatecourse.pm 
    /loncom/misc	releaseslist.xml 
    /loncom/lonnet/perl	lonnet.pm 
    /doc/loncapafiles	loncapafiles.lpml 
    /loncom/enrollment	localenroll.pm 
    /loncom/auth	lonroles.pm lonauth.pm 
  Log:
  - Bug 6808. New course container -- "Placement" for Placement Tests.
    Work in progress.
  
  
-------------- next part --------------
Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.270 loncom/interface/domainprefs.pm:1.271
--- loncom/interface/domainprefs.pm:1.270	Fri Feb 19 20:28:46 2016
+++ loncom/interface/domainprefs.pm	Sat Apr  2 04:30:19 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.270 2016/02/19 20:28:46 raeburn Exp $
+# $Id: domainprefs.pm,v 1.271 2016/04/02 04:30:19 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -103,8 +103,8 @@
 
 In the case of course requests, radio buttons are displayed for each institutional
 affiliate type (and also default, and _LC_adv) for each of the course types 
-(official, unofficial, community, and textbook).  In each case the radio buttons 
-allow the selection of one of four values:
+(official, unofficial, community, textbook, and placement).  
+In each case the radio buttons allow the selection of one of four values:  
 
 0, approval, validate, autolimit=N (where N is blank, or a positive integer).
 which have the following effects:
@@ -1724,7 +1724,7 @@
     my $typecount = 0;
     my ($css_class,%titles);
     if ($context eq 'requestcourses') {
-        @usertools = ('official','unofficial','community','textbook');
+        @usertools = ('official','unofficial','community','textbook','placement');
         @options =('norequest','approval','validate','autolimit');
         %validations = &Apache::lonnet::auto_courserequest_checks($dom);
         %titles = &courserequest_titles();
@@ -2176,7 +2176,7 @@
     my ($settings,$rowtotal) = @_;
     my $rownum = 0; 
     my ($output,%current);
-    my @crstypes = ('official','unofficial','community','textbook');
+    my @crstypes = ('official','unofficial','community','textbook','placement');
     if (ref($settings) eq 'HASH') {
         if (ref($settings->{'uniquecode'}) eq 'HASH') {
             foreach my $type (@crstypes) {
@@ -3403,7 +3403,7 @@
         my ($currdefresponder,%defcredits,%curruploadquota,%deftimeout);
         my $currusecredits = 0;
         my $postsubmitclient = 1;
-        my @types = ('official','unofficial','community','textbook');
+        my @types = ('official','unofficial','community','textbook','placement');
         if (ref($settings) eq 'HASH') {
             $currdefresponder = $settings->{'anonsurvey_threshold'};
             if (ref($settings->{'uploadquota'}) eq 'HASH') {
@@ -3537,7 +3537,7 @@
     my ($position,$dom,$settings,$rowtotal) = @_;
     my ($css_class,$datatable);
     my $itemcount = 1;
-    my @types = ('official','unofficial','community','textbook');
+    my @types = ('official','unofficial','community','textbook','placement');
     if (($position eq 'top') || ($position eq 'middle')) {
         my ($rowsref,$titlesref) = &Apache::lonuserutils::get_selfenroll_titles();
         my %descs = &Apache::lonuserutils::selfenroll_default_descs();
@@ -4556,6 +4556,7 @@
                      unofficial => 'Unofficial courses',
                      community  => 'Communities',
                      textbook   => 'Textbook courses',
+                     placement  => 'Placement tests',
                  );
     return %titles;
 }
@@ -4566,6 +4567,7 @@
                                    unofficial => 'Unofficial',
                                    community  => 'Communities',
                                    textbook   => 'Textbook',
+                                   placement  => 'Placement tests',
                                    norequest  => 'Not allowed',
                                    approval   => 'Approval by Dom. Coord.',
                                    validate   => 'With validation',
@@ -7595,7 +7597,7 @@
         $context = $action;
     }
     if ($context eq 'requestcourses') {
-        @usertools = ('official','unofficial','community','textbook');
+        @usertools = ('official','unofficial','community','textbook','placement');
         @options =('norequest','approval','validate','autolimit');
         %validations = &Apache::lonnet::auto_courserequest_checks($dom);
         %titles = &courserequest_titles();
@@ -7644,7 +7646,7 @@
         my @approvalnotify = &Apache::loncommon::get_env_multiple('form.'.$context.'notifyapproval');
         @approvalnotify = sort(@approvalnotify);
         $confhash{'notify'}{'approval'} = join(',', at approvalnotify);
-        my @crstypes = ('official','unofficial','community','textbook');
+        my @crstypes = ('official','unofficial','community','textbook','placement');
         my @hasuniquecode = &Apache::loncommon::get_env_multiple('form.uniquecode');
         foreach my $type (@hasuniquecode) {
             if (grep(/^\Q$type\E$/, at crstypes)) {
@@ -11282,8 +11284,8 @@
                          );
     my @toggles = ('canuse_pdfforms','uselcmath','usejsme');
     my @numbers = ('anonsurvey_threshold','uploadquota_official','uploadquota_unofficial',
-                   'uploadquota_community','uploadquota_textbook');
-    my @types = ('official','unofficial','community','textbook');
+                   'uploadquota_community','uploadquota_textbook','uploadquota_placement');
+    my @types = ('official','unofficial','community','textbook','placement');
     my %staticdefaults = (
                            anonsurvey_threshold => 10,
                            uploadquota          => 500,
@@ -11546,7 +11548,7 @@
                                        '<li>'.&mt('Official courses: [_1] MB','<b>'.$defaultshash{'coursedefaults'}{'uploadquota'}{'official'}.'</b>').'</li>'.
                                        '<li>'.&mt('Unofficial courses: [_1] MB','<b>'.$defaultshash{'coursedefaults'}{'uploadquota'}{'unofficial'}.'</b>').'</li>'.
                                        '<li>'.&mt('Textbook courses: [_1] MB','<b>'.$defaultshash{'coursedefaults'}{'uploadquota'}{'textbook'}.'</b>').'</li>'.
-
+                                       '<li>'.&mt('Placement tests: [_1] MB','<b>'.$defaultshash{'coursedefaults'}{'uploadquota'}{'placement'}.'</b>').'</li>'. 
                                        '<li>'.&mt('Communities: [_1] MB','<b>'.$defaultshash{'coursedefaults'}{'uploadquota'}{'community'}.'</b>').'</li>'.
                                        '</ul>'.
                                        '</li>';
@@ -11582,6 +11584,8 @@
                                     $resulttext .= &mt('Unofficial courses');
                                 } elsif ($type eq 'textbook') {
                                     $resulttext .= &mt('Textbook courses');
+                                } elsif ($type eq 'placement') {
+                                    $resulttext .= &mt('Placement tests');
                                 }
                                 $resulttext .= ' -- '.$display.'</li>';
                             }
@@ -11633,7 +11637,7 @@
 sub modify_selfenrollment {
     my ($dom,$lastactref,%domconfig) = @_;
     my ($resulttext,$errors,%changes,%selfenrollhash,%ordered);
-    my @types = ('official','unofficial','community','textbook');
+    my @types = ('official','unofficial','community','textbook','placement');
     my %titles = &tool_titles();
     my %descs = &Apache::lonuserutils::selfenroll_default_descs();
     ($ordered{'admin'},my $titlesref) = &Apache::lonuserutils::get_selfenroll_titles();
Index: loncom/interface/loncommon.pm
diff -u loncom/interface/loncommon.pm:1.1236 loncom/interface/loncommon.pm:1.1237
--- loncom/interface/loncommon.pm:1.1236	Fri Mar  4 21:43:15 2016
+++ loncom/interface/loncommon.pm	Sat Apr  2 04:30:20 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1236 2016/03/04 21:43:15 raeburn Exp $
+# $Id: loncommon.pm,v 1.1237 2016/04/02 04:30:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -5573,7 +5573,17 @@
         $dc_info =~ s/\s+$//;
     }
 
-    $role = '<span class="LC_nobreak">('.$role.')</span>' if $role;
+    my $crstype;
+    if ($env{'request.course.id'}) {
+        $crstype = $env{'course.'.$env{'request.course.id'}.'.type'};
+    } elsif ($args->{'crstype'}) {
+        $crstype = $args->{'crstype'};
+    }
+    if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) {
+        undef($role);
+    } else {
+        $role = '<span class="LC_nobreak">('.$role.')</span>' if $role;
+    }
 
         if ($env{'request.state'} eq 'construct') { $forcereg=1; }
 
@@ -5584,7 +5594,7 @@
         $bodytag .= Apache::lonhtmlcommon::scripttag(
             Apache::lonmenu::utilityfunctions($httphost), 'start');
 
-        my ($left,$right) = Apache::lonmenu::primary_menu();
+        my ($left,$right) = Apache::lonmenu::primary_menu($crstype);
 
         if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
              if ($dc_info) {
@@ -8193,7 +8203,10 @@
 		#if bread_crumbs_component exists show it as headline else show only the breadcrumbs
 		if(exists($args->{'bread_crumbs_component'})){
 			$result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'});
-		}else{
+		} elsif ($args->{'crstype'} eq 'Placement') {
+			$result .= &Apache::lonhtmlcommon::breadcrumbs('','','','','','','','','',
+                                                                       $args->{'crstype'});
+                } else {
 			$result .= &Apache::lonhtmlcommon::breadcrumbs();
 		}
     }
@@ -9310,8 +9323,8 @@
 2. user's domain
 3. quota name - portfolio, author, or course
    (if no quota name provided, defaults to portfolio).
-4. crstype - official, unofficial, textbook or community, if quota name is
-   course
+4. crstype - official, unofficial, textbook, placement or community, 
+   if quota name is course
 
 Returns:
 1. Disk quota (in MB) assigned to student.
@@ -9385,7 +9398,8 @@
             if ($quotaname eq 'course') {
                 my %domdefs = &Apache::lonnet::get_domain_defaults($udom);
                 if (($crstype eq 'official') || ($crstype eq 'unofficial') || 
-                    ($crstype eq 'community') || ($crstype eq 'textbook')) { 
+                    ($crstype eq 'community') || ($crstype eq 'textbook') ||
+                    ($crstype eq 'placement')) { 
                     $defquota = $domdefs{$crstype.'quota'};
                 }
                 if ($defquota eq '') {
@@ -9533,7 +9547,7 @@
 4. filename of file for which action is being requested
 5. filesize (kB) of file
 6. action being taken: copy or upload.
-7. quotatype (in course context -- official, unofficial, community or textbook).
+7. quotatype (in course context -- official, unofficial, textbook, placement or community).
 
 Returns: 1 scalar: HTML to display containing warning if quota would be exceeded,
          otherwise return null.
@@ -15183,6 +15197,17 @@
         $outcome .= ($fatal?$errtext:'write ok').$linefeed;
     }
 
+# 
+# Set params for Placement Tests
+#
+    if ($crstype eq 'Placement') {
+       my $storeunder=$$crsudom.'_'.$$crsunum.'.0.buttonshide';
+       my %storecontent = ($storeunder         => 'yes',
+                           $storeunder.'.type' => 'string_yesno');
+       &Apache::lonnet::cput
+                 ('resourcedata',\%storecontent,$$crsudom,$$crsunum); 
+    }
+
     return (1,$outcome);
 }
 
@@ -15243,8 +15268,7 @@
 ############################################################
 ############################################################
 
-#SD
-# only Community and Course, or anything else?
+# Community, Course and Placement Test
 sub course_type {
     my ($cid) = @_;
     if (!defined($cid)) {
@@ -15262,17 +15286,19 @@
     my %names = (
                   'Course' => 'group',
                   'Community' => 'group',
+                  'Placement' => 'group',
                 );
     return $names{$crstype};
 }
 
 sub course_types {
-    my @types = ('official','unofficial','community','textbook');
+    my @types = ('official','unofficial','community','textbook','placement');
     my %typename = (
                          official   => 'Official course',
                          unofficial => 'Unofficial course',
                          community  => 'Community',
                          textbook   => 'Textbook course',
+                         placement  => 'Placement test',
                    );
     return (\@types,\%typename);
 }
@@ -15487,7 +15513,7 @@
                                                   undef,\%userenv,\%domdef,\%is_adv);
         }
 
-        foreach my $crstype ('official','unofficial','community','textbook') {
+        foreach my $crstype ('official','unofficial','community','textbook','placement') {
             $userenv{'canrequest.'.$crstype} =
                 &Apache::lonnet::usertools_access($username,$domain,$crstype,
                                                   'reload','requestcourses',
@@ -15759,7 +15785,7 @@
             $typeselectform .= ' onchange="'.$onchange.'"';
         }
         $typeselectform .= '>'."\n";
-        foreach my $posstype ('Course','Community') {
+        foreach my $posstype ('Course','Community','Placement') {
             $typeselectform.='<option value="'.$posstype.'"'.
                 ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".&mt($posstype)."</option>\n";
         }
Index: loncom/interface/loncreateuser.pm
diff -u loncom/interface/loncreateuser.pm:1.410 loncom/interface/loncreateuser.pm:1.411
--- loncom/interface/loncreateuser.pm:1.410	Fri Feb 19 20:28:46 2016
+++ loncom/interface/loncreateuser.pm	Sat Apr  2 04:30:20 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Create a user
 #
-# $Id: loncreateuser.pm,v 1.410 2016/02/19 20:28:46 raeburn Exp $
+# $Id: loncreateuser.pm,v 1.411 2016/04/02 04:30:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -251,13 +251,15 @@
                    'unofficial' => 'Can request creation of unofficial courses',
                    'community'  => 'Can request creation of communities',
                    'textbook'   => 'Can request creation of textbook courses',
+                   'placement'  => 'Can request creation of placement tests',
                    'requestauthor'  => 'Can request author space',
     );
     if ($context eq 'requestcourses') {
         %userenv = &Apache::lonnet::userenvironment($ccdomain,$ccuname,
                       'requestcourses.official','requestcourses.unofficial',
-                      'requestcourses.community','requestcourses.textbook');
-        @usertools = ('official','unofficial','community','textbook');
+                      'requestcourses.community','requestcourses.textbook',
+                      'requestcourses.placement');
+        @usertools = ('official','unofficial','community','textbook','placement');
         @options =('norequest','approval','autolimit','validate');
         %validations = &Apache::lonnet::auto_courserequest_checks($ccdomain);
         %reqtitles = &courserequest_titles();
@@ -447,12 +449,14 @@
                    'unofficial' => 'Can request creation of unofficial courses',
                    'community'  => 'Can request creation of communities',
                    'textbook'   => 'Can request creation of textbook courses',
+                   'placement'  => 'Can request creation of placement tests',
     );
 
     %userenv = &Apache::lonnet::userenvironment($ccdomain,$ccuname,
                       'reqcrsotherdom.official','reqcrsotherdom.unofficial',
-                      'reqcrsotherdom.community','reqcrsotherdom.textbook');
-    @usertools = ('official','unofficial','community','textbook');
+                      'reqcrsotherdom.community','reqcrsotherdom.textbook',
+                      'reqcrsotherdom.placement');
+    @usertools = ('official','unofficial','community','textbook','placement');
     @options = ('approval','validate','autolimit');
     %validations = &Apache::lonnet::auto_courserequest_checks($cdom);
     my $optregex = join('|', at options);
@@ -533,6 +537,7 @@
                                    unofficial => 'Unofficial',
                                    community  => 'Communities',
                                    textbook   => 'Textbook',
+                                   placement  => 'Placement Tests',
                                    norequest  => 'Not allowed',
                                    approval   => 'Approval by Dom. Coord.',
                                    validate   => 'With validation',
@@ -2579,7 +2584,7 @@
     my (%alerts,%rulematch,%inst_results,%curr_rules);
     my @userinfo = ('firstname','middlename','lastname','generation','permanentemail','id');
     my @usertools = ('aboutme','blog','webdav','portfolio');
-    my @requestcourses = ('official','unofficial','community','textbook');
+    my @requestcourses = ('official','unofficial','community','textbook','placement');
     my @requestauthor = ('requestauthor');
     my ($othertitle,$usertypes,$types) = 
         &Apache::loncommon::sorted_inst_types($env{'form.ccdomain'});
@@ -2752,7 +2757,7 @@
              'requestcourses.community','requestcourses.textbook',
              'reqcrsotherdom.official','reqcrsotherdom.unofficial',
              'reqcrsotherdom.community','reqcrsotherdom.textbook',
-             'requestauthor'],
+             'reqcrsotherdom.placement','requestauthor'],
               $env{'form.ccdomain'},$env{'form.ccuname'});
         my ($tmp) = keys(%userenv);
         if ($tmp =~ /^(con_lost|error)/i) { 
@@ -3043,8 +3048,9 @@
                         ($env{'user.domain'} eq $env{'form.ccdomain'})) {
                         my %newenvhash;
                         foreach my $key (keys(%changed)) {
-                            if (($key eq 'official') || ($key eq 'unofficial')
-                                || ($key eq 'community') || ($key eq 'textbook')) {
+                            if (($key eq 'official') || ($key eq 'unofficial') ||
+                                ($key eq 'community') || ($key eq 'textbook') ||
+                                ($key eq 'placement')) {
                                 $newenvhash{'environment.requestcourses.'.$key} =
                                     $changeHash{'requestcourses.'.$key};
                                 if ($changeHash{'requestcourses.'.$key}) {
@@ -3253,6 +3259,7 @@
          'unofficial'     => 'Can Request Unofficial Courses',
          'community'      => 'Can Request Communities',
          'textbook'       => 'Can Request Textbook Courses',
+         'placement'      => 'Can Request Placement Tests',
          'requestauthor'  => 'Can Request Author Role',
          'inststatus'     => "Affiliation",
          'prvs'           => 'Previous Value:',
@@ -5400,6 +5407,7 @@
                                           groups => 'Community Groups',
                                         },
                        );
+        $linktext{'Placement'} = $linktext{'Course'};
 
         my %linktitle = (
             'Course' => {
@@ -5414,6 +5422,8 @@
                            },
         );
 
+        $linktitle{'Placement'} = $linktitle{'Course'};
+
         push(@{ $menu[0]->{items} }, #Category: Single Users
             {   
              linktext => $linktext{$crstype}{'single'},
Index: loncom/interface/londocs.pm
diff -u loncom/interface/londocs.pm:1.600 loncom/interface/londocs.pm:1.601
--- loncom/interface/londocs.pm:1.600	Sun Mar 27 20:22:52 2016
+++ loncom/interface/londocs.pm	Sat Apr  2 04:30:20 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Documents
 #
-# $Id: londocs.pm,v 1.600 2016/03/27 20:22:52 raeburn Exp $
+# $Id: londocs.pm,v 1.601 2016/04/02 04:30:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -3429,7 +3429,9 @@
     }
     my $quotatype = 'unofficial';
     if ($crstype eq 'Community') {
-        $quotatype = 'community';    
+        $quotatype = 'community';
+    } elsif ($crstype eq 'Placement') {
+        $quotatype = 'placement';
     } elsif ($env{'course.'.$coursedom.'_'.$coursenum.'.internal.coursecode'}) {
         $quotatype = 'official';
     } elsif ($env{'course.'.$coursedom.'_'.$coursenum.'.internal.textbook'}) {
@@ -5151,7 +5153,9 @@
     # file size to determine if upload should be allowed.
     my $quotatype = 'unofficial';
     if ($crstype eq 'Community') {
-        $quotatype = 'community';    
+        $quotatype = 'community';
+    } elsif ($crstype eq 'Placement') {
+        $quotatype = 'placement';
     } elsif ($env{'course.'.$coursedom.'_'.$coursenum.'.internal.coursecode'}) {
         $quotatype = 'official';
     } elsif ($env{'course.'.$coursedom.'_'.$coursenum.'.internal.textbook'}) {
Index: loncom/interface/lonhtmlcommon.pm
diff -u loncom/interface/lonhtmlcommon.pm:1.370 loncom/interface/lonhtmlcommon.pm:1.371
--- loncom/interface/lonhtmlcommon.pm:1.370	Tue Jan 26 14:30:25 2016
+++ loncom/interface/lonhtmlcommon.pm	Sat Apr  2 04:30:20 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.370 2016/01/26 14:30:25 raeburn Exp $
+# $Id: lonhtmlcommon.pm,v 1.371 2016/04/02 04:30:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1704,7 +1704,8 @@
     if ($env{'request.noversionuri'}=~m{^/priv/} ||
         $env{'request.uri'}=~m{^/priv/}) { return 1; }
     return if ($env{'request.noversionuri'} eq '/adm/supplemental');
-
+    return if (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') &&
+               (!$env{'request.role.adv'}));
     if (($env{'request.noversionuri'} =~ m{^/adm/(viewclasslist|navmaps)($|\?)})
         || ($env{'request.noversionuri'} =~ m{^/adm/.*/aboutme($|\?)})) {
 
@@ -1868,7 +1869,7 @@
     
     sub breadcrumbs {
         my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
-            $CourseBreadcrumbs,$topic_help,$topic_help_text) = @_;
+            $CourseBreadcrumbs,$topic_help,$topic_help_text,$crstype) = @_;
         #
         $css_class ||= 'LC_breadcrumbs';
 
@@ -1883,6 +1884,11 @@
         #
         # The first one should be the course or a menu link
         if (!defined($menulink)) { $menulink=1; }
+        if ((($crstype eq 'Placement') || (($env{'request.course.id'}) &&
+            ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement'))) &&
+            (!$env{'request.role.adv'})) {
+            undef($menulink);
+        }
         if ($menulink) {
             my $description = 'Menu';
             my $no_mt_descr = 0;
Index: loncom/interface/lonmenu.pm
diff -u loncom/interface/lonmenu.pm:1.442 loncom/interface/lonmenu.pm:1.443
--- loncom/interface/lonmenu.pm:1.442	Thu Mar 17 13:51:28 2016
+++ loncom/interface/lonmenu.pm	Sat Apr  2 04:30:20 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Routines to control the menu
 #
-# $Id: lonmenu.pm,v 1.442 2016/03/17 13:51:28 raeburn Exp $
+# $Id: lonmenu.pm,v 1.443 2016/04/02 04:30:20 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -238,6 +238,7 @@
 # @primary_menu is filled within the BEGIN block of this module with 
 # entries from mydesk.tab
 sub primary_menu {
+    my ($crstype) = @_;
     my (%menu);
     # each element of @primary contains following array:
     # (link url, icon path, alt text, link text, condition, position)
@@ -246,6 +247,15 @@
         || (($env{'user.name'} eq '') && ($env{'user.domain'} eq ''))) {
         $public = 1;
     }
+    my $rolecount;
+    if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) {
+        my $update=$env{'user.update.time'};
+        if (!$update) {
+            $update = $env{'user.login.time'};
+        }
+        my %roles_in_env;
+        $rolecount = &Apache::lonroles::roles_from_env(\%roles_in_env,$update);
+    }
     foreach my $menuitem (@primary_menu) {
         # evaluate conditions 
         next if    ref($menuitem)       ne 'ARRAY';    #
@@ -263,8 +273,14 @@
                 && &Apache::loncommon::show_course();  ##term 'Courses' or 
         next if    $$menuitem[4]        eq 'courses'   ##'Roles' wanted
                 && !&Apache::loncommon::show_course(); ##
-        
         my $title = $menuitem->[3];
+        if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) {
+            if ($menuitem->[4] eq 'courses') {
+                next unless ($rolecount>1);
+            } else {
+                next unless (($title eq 'Personal') || ($title eq 'Logout'));
+            }
+        }
         my $position = $menuitem->[5];
         if ($position eq '') {
             $position = 'right';
@@ -280,6 +296,7 @@
             my @primsub;
             if (ref($primary_submenu{$title}) eq 'ARRAY') {
                 foreach my $item (@{$primary_submenu{$title}}) {
+                    next if (($crstype eq 'Placement') && (!$env{'request.role.adv'}));
                     next if (($item->[2] eq 'wishlist') && (!$env{'user.adv'}));
                     next if ((($item->[2] eq 'portfolio') ||
                              ($item->[2] eq 'blog')) &&
@@ -287,18 +304,19 @@
                                                            undef,'tools')));
                     push(@primsub,$item);
                 }
+                if ($title eq 'Personal' && $env{'user.name'} && $env{'user.domain'} ) {
+                    $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'});
+                } else {
+                    $title = &mt($title);
+                }
                 if (@primsub > 0) {
-                    if ($title eq 'Personal' && $env{'user.name'} && $env{'user.domain'} ) {
-                        $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'});
-                    } else {
-                        $title = &mt($title);
-                    }
                     $menu{$position} .= &create_submenu($link,$target,$title,\@primsub,1);
                 } elsif ($link) {
-                    $menu{$position} .= '<li><a href="'.$link.'" target="'.$target.'">'.&mt($title).'</a></li>';
+                    $menu{$position} .= '<li><a href="'.$link.'" target="'.$target.'">'.$title.'</a></li>';
                 }
             }
         } elsif ($$menuitem[3] eq 'Help') { # special treatment for helplink
+            next if ($crstype eq 'Placement'); 
             if ($public) {
                 my $origmail = $Apache::lonnet::perlvar{'lonSupportEMail'};
                 my $defdom = &Apache::lonnet::default_login_domain();
@@ -424,6 +442,7 @@
     foreach my $menuitem (@secondary_menu) {
         # evaluate conditions 
         next if    ref($menuitem)  ne 'ARRAY';
+        next if (($crstype eq 'Placement') && ($$menuitem[3] ne 'Roles') && (!$env{'request.role.adv'}));
         next if    $$menuitem[4]   ne 'always'
                 && ($$menuitem[4]   ne 'author' && $$menuitem[4] ne 'cca')
                 && !$env{'request.course.id'};
@@ -609,25 +628,25 @@
 
     undef(@inlineremote);
 
-    my ($mapurl,$resurl);
+    my ($mapurl,$resurl,$crstype);
 
     if ($env{'request.course.id'}) {
+#
+#course_type:  Course, Community, or Placement
+#
+        $crstype = &Apache::loncommon::course_type();
         if ($env{'request.symb'}) {
             ($mapurl, my $rid, $resurl) = &Apache::lonnet::decode_symb(&Apache::lonnet::symbread());
             my $coursetitle = $env{'course.'.$env{'request.course.id'}.'.description'};
 
             my $maptitle = &Apache::lonnet::gettitle($mapurl);
             my $restitle = &Apache::lonnet::gettitle(&Apache::lonnet::symbread());
-
-#SD
-#course_type only Course and Community?
-#
             my @crumbs;
             unless (($forcereg) &&
                     ($env{'request.noversionuri'} eq '/adm/navmaps') &&
-                    ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'})) {
-                @crumbs = ({text  => Apache::loncommon::course_type() 
-                                    . ' Contents', 
+                    ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) ||
+                    (($crstype eq 'Placement') && (!$env{'request.role.adv'}))) {
+                @crumbs = ({text  => $crstype.' Contents', 
                             href  => "Javascript:gopost('/adm/navmaps','')"});
             }
             if ($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'}) { 
@@ -635,9 +654,11 @@
                                no_mt => 1});
             }
 
-            push @crumbs, {text => $maptitle, no_mt => 1} if ($maptitle 
-                                                       && $maptitle ne 'default.sequence' 
-                                                       && $maptitle ne $coursetitle);
+            unless (($crstype eq 'Placement') || (!$env{'request.role.adv'})) {
+                push @crumbs, {text => $maptitle, no_mt => 1} if ($maptitle 
+                                                           && $maptitle ne 'default.sequence' 
+                                                           && $maptitle ne $coursetitle);
+            }
 
             push @crumbs, {text => $restitle, no_mt => 1} if $restitle; 
             my @tools;
@@ -655,7 +676,6 @@
         } else {
             $resurl = $env{'request.noversionuri'};
             my $courseurl = &Apache::lonnet::courseid_to_courseurl($env{'request.course.id'});
-            my $crstype = &Apache::loncommon::course_type();
             my $title = &mt('View Resource');
             if ($resurl =~ m{^\Q/uploaded$courseurl/supplemental/\E(default|\d+)/}) {
                 &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['folderpath','title']);
@@ -817,9 +837,13 @@
 # We are in a course and looking at a registered URL
 # Should probably be in mydesk.tab
 #
-	    $menuitems=(<<ENDMENUITEMS);
-c&3&1
-s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1
+            $menuitems = "c&3&1";
+            if (($crstype ne 'Placement') || ($env{'request.role.adv'})) {
+                $menuitems.="
+s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1";
+            }
+	    $menuitems .= (<<ENDMENUITEMS);
+
 s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3
 c&6&3
 c&8&1
@@ -1501,7 +1525,7 @@
 
 sub check_for_rcrs {
     my $showreqcrs = 0;
-    my @reqtypes = ('official','unofficial','community','textbook');
+    my @reqtypes = ('official','unofficial','community','textbook','placement');
     foreach my $type (@reqtypes) {
         if (&Apache::lonnet::usertools_access($env{'user.name'},
                                               $env{'user.domain'},
Index: loncom/interface/lonmodifycourse.pm
diff -u loncom/interface/lonmodifycourse.pm:1.79 loncom/interface/lonmodifycourse.pm:1.80
--- loncom/interface/lonmodifycourse.pm:1.79	Tue Mar 29 14:05:10 2016
+++ loncom/interface/lonmodifycourse.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # handler for DC-only modifiable course settings
 #
-# $Id: lonmodifycourse.pm,v 1.79 2016/03/29 14:05:10 raeburn Exp $
+# $Id: lonmodifycourse.pm,v 1.80 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -634,7 +634,7 @@
     my ($cdom,$type,$settings) = @_;
     return unless (ref($settings) eq 'HASH'); 
     my $lctype = lc($type);
-    unless ($type eq 'Community') {
+    unless (($type eq 'Community') || ($type eq 'Placement')) {
         $lctype = 'unofficial';
         if ($settings->{'internal.coursecode'}) {
             $lctype = 'official';
Index: loncom/interface/lonnavdisplay.pm
diff -u loncom/interface/lonnavdisplay.pm:1.32 loncom/interface/lonnavdisplay.pm:1.33
--- loncom/interface/lonnavdisplay.pm:1.32	Mon Mar 30 22:29:24 2015
+++ loncom/interface/lonnavdisplay.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Navigate Maps Display Handler
 #
-# $Id: lonnavdisplay.pm,v 1.32 2015/03/30 22:29:24 raeburn Exp $
+# $Id: lonnavdisplay.pm,v 1.33 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -83,6 +83,14 @@
         }
     }
 
+    my $course_type = &Apache::loncommon::course_type();
+    if (($course_type eq 'Placement') && (!$env{'request.role.adv'})) { 
+        my $furl = &Apache::lonpageflip::first_accessible_resource();
+        &Apache::loncommon::content_type($r,'text/html');
+        $r->header_out(Location => $furl);
+        return REDIRECT;
+    }
+
     # Create the nav map
     my $navmap = Apache::lonnavmaps::navmap->new();
 
Index: loncom/interface/lonpickcourse.pm
diff -u loncom/interface/lonpickcourse.pm:1.116 loncom/interface/lonpickcourse.pm:1.117
--- loncom/interface/lonpickcourse.pm:1.116	Thu May 21 23:40:09 2015
+++ loncom/interface/lonpickcourse.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Pick a course
 #
-# $Id: lonpickcourse.pm,v 1.116 2015/05/21 23:40:09 raeburn Exp $
+# $Id: lonpickcourse.pm,v 1.117 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -55,7 +55,7 @@
     my ($type,$title,$jscript,$multelement,$multiple,$roleelement,$typeelement,
         $lastaction,$autosubmit,$submitopener,$cloneruname,$clonerudom,$crscode,$crsdom);
 
-    # Get course type - Course or Community.
+    # Get course type - Course, Community or Placement.
     $type = $env{'form.type'};
     if (!defined($env{'form.type'})) {
         $type = 'Course';
@@ -457,7 +457,7 @@
         $r->print('<th>'.&mt('Select').'</th>'
                  .'<th>'.$titlehdr.'</th>'
                  .'<th>'.&mt('Domain').'</th>');
-        unless ($type eq 'Community') {
+        unless (($type eq 'Community') || ($type eq 'Placement')) {
             $r->print('<th>'.&mt('Course Code').'</th>');
         }
         $r->print('<th>'.&mt('Owner/Co-owner(s)').'</th>');
@@ -608,7 +608,7 @@
             $r->print(&Apache::lonnet::domain($cdom,'description')?
                       $cdom.' ('.&Apache::lonnet::domain($cdom,'description').')':$cdom);
             $r->print('</td>');
-            unless ($type eq 'Community') { 
+            unless (($type eq 'Community') || ($type eq 'Placement')) { 
                 $r->print('<td>');
                 if ($instcode ne '') {
                     $r->print(&unescape($instcode));
@@ -706,7 +706,7 @@
         my ($cc_clone,$ccrole);
         if ($type eq 'Community') {
             $ccrole = 'co';
-        } elsif ($type eq 'Course') {
+        } else {
             $ccrole = 'cc';
         }
         my %ccroles = &Apache::lonnet::get_my_roles($cloneruname,$clonerudom,
@@ -751,6 +751,11 @@
                                  total => 'coursetotal',
                                  list  => 'courselist',
                               },
+                     'Placement' => {
+                                 name  => 'coursepick',
+                                 total => 'coursetotal',
+                                 list  => 'courselist',
+                                 },
                     );
     my $output = qq|
 function gochoose(cname,cdom,cdesc) {
@@ -872,7 +877,7 @@
 Course Domain - the domain of the course
 
 =item *
-Type - Course or Community
+Type - Course, Community or Placement
 
 =item *
 Course Institutional Code - the institutional identifier assigned to the course
@@ -925,7 +930,7 @@
 X<create_user_javascript()>
 B<create_user_javascript($type)>:
 
-Input: 1 - $type  - the course type - Course or Community
+Input: 1 - $type  - the course type - Course, Community, or Placement
 
 Output: 1 - $output - javascript wrapped in E<lt>scriptE<gt>E<lt>/scriptE<gt> tags 
 
Index: loncom/interface/lonquickgrades.pm
diff -u loncom/interface/lonquickgrades.pm:1.108 loncom/interface/lonquickgrades.pm:1.109
--- loncom/interface/lonquickgrades.pm:1.108	Sun Jan 31 21:25:38 2016
+++ loncom/interface/lonquickgrades.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Quick Student Grades Display
 #
-# $Id: lonquickgrades.pm,v 1.108 2016/01/31 21:25:38 raeburn Exp $
+# $Id: lonquickgrades.pm,v 1.109 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -106,6 +106,12 @@
                 $env{'user.reinit'} = 1;
                 return HTTP_NOT_ACCEPTABLE;
             }
+        } elsif ((&Apache::loncommon::course_type() eq 'Placement') &&
+                 (!$env{'request.role.adv'})) {
+            my $furl = &Apache::lonpageflip::first_accessible_resource();
+            &Apache::loncommon::content_type($r,'text/html');
+            $r->header_out(Location => $furl);
+            return REDIRECT;
         }
     }
 
Index: loncom/interface/lonrequestcourse.pm
diff -u loncom/interface/lonrequestcourse.pm:1.95 loncom/interface/lonrequestcourse.pm:1.96
--- loncom/interface/lonrequestcourse.pm:1.95	Tue Sep  1 16:40:20 2015
+++ loncom/interface/lonrequestcourse.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # Request a course
 #
-# $Id: lonrequestcourse.pm,v 1.95 2015/09/01 16:40:20 raeburn Exp $
+# $Id: lonrequestcourse.pm,v 1.96 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -760,6 +760,7 @@
     var unofficial = '';
     var community = '';
     var textbook = '';
+    var placement = '';
 END
     if (ref($can_request) eq 'HASH') {
         foreach my $item (keys(%{$can_request})) {
@@ -773,6 +774,7 @@
         unofficial => 'You are not permitted to request creation of an unofficial course in this domain.',
         community => 'You are not permitted to request creation of a community in this domain.',
         textbook => 'You are not permitted to request creation of a textbook course in this domain',
+        placement => 'You are not permitted to request creation of a placement test in this domain',
         all => 'You must choose a specific course type when making a new course request.',
         allt => '"All types" is not allowed.',
     ); 
@@ -802,9 +804,16 @@
                         return false;
                     }
                 } else {
-                    if (actionchoice == 'new') {
-                        alert('$js_lt{'all'}'+'\\n'+'$js_lt{'allt'}');
-                        return false;
+                    if (crschoice == 'placement') {
+                        if (placement != 1) {
+                            alert("$js_lt{'placement'}");
+                            return false;
+                        }
+                    } else {
+                        if (actionchoice == 'new') {
+                            alert('$js_lt{'all'}'+'\\n'+'$js_lt{'allt'}');
+                            return false;
+                        }
                     }
                 }
             }
@@ -815,7 +824,7 @@
 END
     my ($pagetitle,$pageinfo,$domaintitle,$earlyout);
     if (ref($can_request) eq 'HASH') {
-        if (($can_request->{'official'}) || ($can_request->{'unofficial'}) || $can_request->{'textbook'}) {
+        if (($can_request->{'official'}) || ($can_request->{'unofficial'}) || ($can_request->{'textbook'}) || ($can_request->{'placement'})) {
             if ($can_request->{'community'}) {
                 $pagetitle = 'Course/Community Requests';
                 $pageinfo = &mt('Request creation of a new course or community, or review your pending requests.');
@@ -992,6 +1001,8 @@
                 $title = &mt('Pending requests for unofficial courses');
             } elsif ($env{'form.crstype'} eq 'textbook') {
                 $title = &mt('Pending requests for textbook courses');
+            } elsif ($env{'form.crstype'} eq 'textbook') {
+                $title = &mt('Pending requests for placement tests'); 
             } else {
                 $title = &mt('Pending course/community requests'); 
             }
@@ -2093,7 +2104,8 @@
             official => 'Requestor is automatically assigned Course Coordinator role.',
         );
         $lt{'unofficial'} = $lt{'official'};
-        $lt{'textbook'} = $lt{'textbook'};
+        $lt{'textbook'} = $lt{'official'};
+        $lt{'placement'} = $lt{'official'};
         $output .= &Apache::lonhtmlcommon::row_headline().
                   '<h3>'.&Apache::loncommon::help_open_topic('Course_Request_Personnel').' '.$lt{$crstype}.' '.&mt('Include other personnel?').'</h3>';
     }
@@ -2504,7 +2516,7 @@
         if (ref($domconfig{'requestcourses'}) eq 'HASH') {
             if (ref($domconfig{'requestcourses'}{'uniquecode'}) eq 'HASH') {
                 if ($curr{'crstype'} eq 'any') {
-                    my @types = qw(official unofficial community textbook);
+                    my @types = qw(official unofficial community textbook placement);
                     foreach my $type (@types) {
                         if ($domconfig{'requestcourses'}{'uniquecode'}{$type}) {
                             $showuniquecode = 1;
@@ -2672,7 +2684,7 @@
                         rejected  => 'Request rejected',
                         cancelled => 'Request cancelled',
             );
-    if (($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook')) {
+    if (($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook') || ($crstype eq 'placement')) {
         $statusnames{'created'} = &mt('Course created');
     } elsif ($crstype eq 'community') {
         $statusnames{'created'} = &mt('Community created');
@@ -3076,6 +3088,7 @@
     &js_escape(\%js_lt);
     $js_lt{'unofficial'} = $js_lt{'official'};
     $js_lt{'textbook'} = $js_lt{'official'};
+    $js_lt{'placement'} = $js_lt{'official'};
     my $js_validate = <<"ENDJS";
 <script type="text/javascript">
 // <![CDATA['
@@ -3644,7 +3657,7 @@
                                                         $instcode,$req_notifylist,\@instsections,\%domconfig);
     return ($result,$output,$customized);
 }
-    
+
 sub process_request {
     my ($r,$lonhost,$dom,$cnum,$crstype,$now,$details,$instcode,$req_notifylist,$instsections,
         $domconfig) = @_; 
@@ -3662,6 +3675,8 @@
             $output = &mt('You are not permitted to request creation of communities');
         } elsif ($crstype eq 'textbook') {
             $output = &mt('You are not permitted to request creation of textbook courses');
+        } elsif ($crstype eq 'placement') {
+            $output = &mt('You are not permitted to request creation of placement tests');
         } else {
             $output = &mt('Unrecognized course type: [_1]',$crstype);
         }
@@ -4299,7 +4314,7 @@
             if (($crstype eq 'community') && 
                 (exists($crsroles{$cnum.':'.$cdom.':co'}))) {
                 $count ++;
-            } elsif ((($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook')) &&
+            } elsif ((($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook') || ($crstype eq 'placement')) &&
                      (exists($crsroles{$cnum.':'.$cdom.':cc'}))) {
                 $count ++;
             }
Index: loncom/interface/lonuserutils.pm
diff -u loncom/interface/lonuserutils.pm:1.172 loncom/interface/lonuserutils.pm:1.173
--- loncom/interface/lonuserutils.pm:1.172	Sun Aug  9 21:43:18 2015
+++ loncom/interface/lonuserutils.pm	Sat Apr  2 04:30:21 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Utility functions for managing LON-CAPA user accounts
 #
-# $Id: lonuserutils.pm,v 1.172 2015/08/09 21:43:18 raeburn Exp $
+# $Id: lonuserutils.pm,v 1.173 2016/04/02 04:30:21 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -6242,6 +6242,8 @@
     }
     if ($crstype eq 'Community') {
         $type = 'community';
+    } elsif ($crstype eq 'Placement') {
+        $type = 'placement';
     } elsif ($settings{'internal.coursecode'}) {
         $type = 'official';
     } elsif ($settings{'internal.textbook'}) {
Index: loncom/lond
diff -u loncom/lond:1.518 loncom/lond:1.519
--- loncom/lond:1.518	Wed Feb 17 19:15:44 2016
+++ loncom/lond	Sat Apr  2 04:30:29 2016
@@ -2,7 +2,7 @@
 # The LearningOnline Network
 # lond "LON Daemon" Server (port "LOND" 5663)
 #
-# $Id: lond,v 1.518 2016/02/17 19:15:44 raeburn Exp $
+# $Id: lond,v 1.519 2016/04/02 04:30:29 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -64,7 +64,7 @@
 my $status='';
 my $lastlog='';
 
-my $VERSION='$Revision: 1.518 $'; #' stupid emacs
+my $VERSION='$Revision: 1.519 $'; #' stupid emacs
 my $remoteVERSION;
 my $currenthostid="default";
 my $currentdomainid;
@@ -5505,7 +5505,7 @@
     my $userinput = "$cmd:$tail";
     my $dom = $tail;
     my $result;
-    my @reqtypes = ('official','unofficial','community','textbook');
+    my @reqtypes = ('official','unofficial','community','textbook','placement');
     eval {
         local($SIG{__DIE__})='DEFAULT';
         my %validations;
Index: loncom/loncapa_apache.conf
diff -u loncom/loncapa_apache.conf:1.243 loncom/loncapa_apache.conf:1.244
--- loncom/loncapa_apache.conf:1.243	Fri Mar 18 18:21:08 2016
+++ loncom/loncapa_apache.conf	Sat Apr  2 04:30:29 2016
@@ -2,7 +2,7 @@
 ## loncapa_apache.conf -- Apache HTTP LON-CAPA configuration file
 ##
 
-# $Id: loncapa_apache.conf,v 1.243 2016/03/18 18:21:08 damieng Exp $
+# $Id: loncapa_apache.conf,v 1.244 2016/04/02 04:30:29 raeburn Exp $
 
 #
 # LON-CAPA Section (extensions to httpd.conf daemon configuration)
@@ -283,6 +283,17 @@
 ErrorDocument     500 /adm/errorhandler
 </LocationMatch>
 
+<LocationMatch "^/adm/placement$">
+AuthType LONCAPA
+Require valid-user
+PerlAuthzHandler        Apache::lonacc
+SetHandler              perl-script
+PerlHandler             Apache::lonplacementtest
+ErrorDocument     404 /adm/notfound.html
+ErrorDocument     406 /adm/notinit.html
+ErrorDocument     500 /adm/errorhandler
+</LocationMatch>
+
 <LocationMatch "^/+priv/.*">
 AuthType LONCAPA
 Require valid-user
Index: loncom/homework/structuretags.pm
diff -u loncom/homework/structuretags.pm:1.545 loncom/homework/structuretags.pm:1.546
--- loncom/homework/structuretags.pm:1.545	Sat Apr  2 04:16:19 2016
+++ loncom/homework/structuretags.pm	Sat Apr  2 04:30:39 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA 
 # definition of tags that give a structure to a document
 #
-# $Id: structuretags.pm,v 1.545 2016/04/02 04:16:19 raeburn Exp $
+# $Id: structuretags.pm,v 1.546 2016/04/02 04:30:39 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -226,9 +226,11 @@
 sub homework_js {
     my ($postsubmit,$timeout);
     if (($env{'request.course.id'}) && ($env{'request.state'} ne 'construct')) {
-        my $crstype;
-        if (&Apache::loncommon::course_type() eq 'Community') {
+        my $crstype = &Apache::loncommon::course_type();
+        if ($crstype eq 'Community') {
             $crstype = 'community';
+        } elsif ($crstype eq 'Placement') {
+            $crstype = 'placement'; 
         } else {
             if ($env{'course.'.$env{'request.course.id'}.'.internal.coursecode'}) {
                 $crstype = 'official';
@@ -1602,7 +1604,8 @@
 	    $form_tag_start.='<hr />';
         } elsif (($env{'request.state'} ne "construct") &&
                  ($Apache::lonhomework::type eq 'randomizetry') &&
-                 ($status eq 'CAN_ANSWER')) {
+                 ($status eq 'CAN_ANSWER') &&
+                 ($env{'course.'.$env{'request.course.id'}.'.type'} ne 'Placement')) {
             my $reqtries = &Apache::lonnet::EXT("resource.$Apache::inputtags::part.randomizeontries");
             my $problemstatus = &get_problem_status($Apache::inputtags::part);
             $form_tag_start.=&randomizetry_problem_header($problemstatus,$reqtries);
@@ -1922,7 +1925,11 @@
                 # Added separately at end of this routine, after added
                 # <script></script> so document will be valid xhtml.
                 #
-		$result.= &Apache::loncommon::end_page({'discussion' => 1,
+                my $showdisc = 1;
+                if ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') { 
+                    $showdisc = 0;
+                }
+		$result.= &Apache::loncommon::end_page({'discussion' => $showdisc,
 							'notbody'    => 1});
 	    } elsif ($target eq 'tex') {
 		my $endminipage = '';
Index: loncom/homework/inputtags.pm
diff -u loncom/homework/inputtags.pm:1.339 loncom/homework/inputtags.pm:1.340
--- loncom/homework/inputtags.pm:1.339	Fri Oct 30 11:21:19 2015
+++ loncom/homework/inputtags.pm	Sat Apr  2 04:30:39 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # input  definitons
 #
-# $Id: inputtags.pm,v 1.339 2015/10/30 11:21:19 raeburn Exp $
+# $Id: inputtags.pm,v 1.340 2016/04/02 04:30:39 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -1336,6 +1336,13 @@
         }
 	$css_class=$possible_class{'no_grade'};
 	$button=1;
+        if ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') {
+            if ($Apache::inputtags::status[-1] eq 'CANNOT_ANSWER') {
+                $message = 'Answer Submitted';
+            } else {
+                undef($message); 
+            }
+        }
     }
     if ($Apache::inputtags::status[-1] eq 'SHOW_ANSWER' && 
 	!$added_computer_text && $target ne 'tex') {
@@ -1636,7 +1643,11 @@
 	if ($target eq 'tex') {
 	    $message='\vskip 2 mm '.$message.' ';
 	} else {
-	    $message="<td class=\"$tdclass $css_class\">$message</td>";
+            if ($message) {
+	        $message="<td class=\"$tdclass $css_class\">$message</td>";
+            } else {
+                $message="<td class=\"$tdclass\"></td>";  
+            }
 	    if ($previousmsg) {
 		$previousmsg="<td class=\"$tdclass LC_answer_previous\">$previousmsg</td>";
 	    }
@@ -1740,7 +1751,11 @@
 	}
 
     }
-    my $output= $previousmsg.$latemessage.$message.$trystr;
+    my $output= $previousmsg.$latemessage.$message;
+    my $crstype = $env{'course.'.$env{'request.course.id'}.'.type'};
+    unless ($crstype eq 'Placement') {
+        $output .= $trystr;
+    }
     if ($output =~ /^\s*$/) {
 	return $button;
     } else {
@@ -1749,7 +1764,7 @@
 	} else {
 	    $output =
 		'<table><tr><td>'.$button.'</td>'.$output;
-	    if (!$no_previous) {
+	    if ((!$no_previous) && ($crstype ne 'Placement')) {
 		$output.='<td class="'.$tdclass.'">'.&previous_tries($id,$target).'</td>';
 	    }
 	    $output.= '</tr></table>';
Index: loncom/automation/batchcreatecourse.pm
diff -u loncom/automation/batchcreatecourse.pm:1.40 loncom/automation/batchcreatecourse.pm:1.41
--- loncom/automation/batchcreatecourse.pm:1.40	Fri Jan  3 18:42:16 2014
+++ loncom/automation/batchcreatecourse.pm	Sat Apr  2 04:30:47 2016
@@ -1,5 +1,5 @@
 #
-# $Id: batchcreatecourse.pm,v 1.40 2014/01/03 18:42:16 raeburn Exp $
+# $Id: batchcreatecourse.pm,v 1.41 2016/04/02 04:30:47 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -163,7 +163,7 @@
 # firstres can be nav, syl, or blank for "Navigate Contents", Syllabus, or
 # no entry respectively.
 # 
-# crstype can be Course or Community
+# crstype can be Course, Community or Placement
 #
 # crsquota is the total disk space permitted for course group portfolio files
 # in all course groups.
@@ -428,7 +428,11 @@
             $rolenames = $longroles->{'Community'};
         }
     } else {
-        $crstype = 'Course';
+        if ($details->{'crstype'} eq 'Placement') {
+            $crstype = $details->{'crstype'};
+        } else {
+            $crstype = 'Course';
+        }
         $ccrole = 'cc';
         if (ref($longroles) eq 'HASH') {
             $rolenames = $longroles->{'Course'};
@@ -451,6 +455,8 @@
     if ($firstres eq '') {
         if ($crstype eq 'Community') {
             $firstres = 'nav';
+        } elsif ($crstype eq 'Placement') {
+            $firstres = 'blank'; 
         } else {
             $firstres = 'syl';
         }
Index: loncom/misc/releaseslist.xml
diff -u loncom/misc/releaseslist.xml:1.14 loncom/misc/releaseslist.xml:1.15
--- loncom/misc/releaseslist.xml:1.14	Fri Mar  4 21:43:24 2016
+++ loncom/misc/releaseslist.xml	Sat Apr  2 04:30:56 2016
@@ -1,4 +1,5 @@
 <course name="crstype" value="Community">2.9</course>
+<course name="crstype" value="Placement">2.12</course>
 <course name="commblock" value="timer">2.11</course>
 <course name="commblock" value="docs">2.11</course>
 <course name="commblock" value="printout">2.11</course>
Index: loncom/lonnet/perl/lonnet.pm
diff -u loncom/lonnet/perl/lonnet.pm:1.1304 loncom/lonnet/perl/lonnet.pm:1.1305
--- loncom/lonnet/perl/lonnet.pm:1.1304	Wed Mar 23 02:19:17 2016
+++ loncom/lonnet/perl/lonnet.pm	Sat Apr  2 04:31:03 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # TCP networking package
 #
-# $Id: lonnet.pm,v 1.1304 2016/03/23 02:19:17 raeburn Exp $
+# $Id: lonnet.pm,v 1.1305 2016/04/02 04:31:03 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2190,7 +2190,7 @@
                                   'coursedefaults','usersessions',
                                   'requestauthor','selfenrollment',
                                   'coursecategories'],$domain);
-    my @coursetypes = ('official','unofficial','community','textbook');
+    my @coursetypes = ('official','unofficial','community','textbook','placement');
     if (ref($domconfig{'defaults'}) eq 'HASH') {
         $domdefaults{'lang_def'} = $domconfig{'defaults'}{'lang_def'}; 
         $domdefaults{'auth_def'} = $domconfig{'defaults'}{'auth_def'};
@@ -2220,7 +2220,7 @@
         }
     }
     if (ref($domconfig{'requestcourses'}) eq 'HASH') {
-        foreach my $item ('official','unofficial','community','textbook') {
+        foreach my $item ('official','unofficial','community','textbook','placement') {
             $domdefaults{$item} = $domconfig{'requestcourses'}{$item};
         }
     }
@@ -6530,6 +6530,7 @@
                       unofficial => 1,
                       community  => 1,
                       textbook   => 1,
+                      placement  => 1,
                  );
     } elsif ($context eq 'requestauthor') {
         %tools = (
@@ -8277,6 +8278,11 @@
     return \%crsreqresponse;
 }
 
+sub auto_export_grades {
+    my ($cnum,$cdom,$gradesref) = @_;
+    return;
+}
+
 sub check_instcode_cloning {
     my ($codedefaults,$code_order,$cloner,$clonefromcode,$clonetocode) = @_;
     unless ((ref($codedefaults) eq 'HASH') && (ref($code_order) eq 'ARRAY')) {
@@ -8498,6 +8504,7 @@
     my %rolenames = (
                       Course    => 'std',
                       Community => 'alt1',
+                      Placement => 'std',
                     );
     if ($cid ne '') {
         if ($env{'course.'.$cid.'.'.$short.'.plaintext'} ne '') {
@@ -14154,7 +14161,7 @@
 =over
 
 =item
-official, unofficial, community, textbook
+official, unofficial, community, textbook, placement
 
 =back
 
@@ -14176,7 +14183,7 @@
 
 =item
 canuse_pdfforms, officialcredits, unofficialcredits, textbookcredits, officialquota, unofficialquota, 
-communityquota, textbookquota
+communityquota, textbookquota, placementquota
 
 =back
 
Index: doc/loncapafiles/loncapafiles.lpml
diff -u doc/loncapafiles/loncapafiles.lpml:1.929 doc/loncapafiles/loncapafiles.lpml:1.930
--- doc/loncapafiles/loncapafiles.lpml:1.929	Wed Mar 23 02:19:36 2016
+++ doc/loncapafiles/loncapafiles.lpml	Sat Apr  2 04:31:16 2016
@@ -2,7 +2,7 @@
  "http://lpml.sourceforge.net/DTD/lpml.dtd">
 <!-- loncapafiles.lpml -->
 
-<!-- $Id: loncapafiles.lpml,v 1.929 2016/03/23 02:19:36 raeburn Exp $ -->
+<!-- $Id: loncapafiles.lpml,v 1.930 2016/04/02 04:31:16 raeburn Exp $ -->
 
 <!--
 
@@ -5828,6 +5828,15 @@
 <status>unverified</status>
 </file>
 <file>
+<source>loncom/interface/lonplacementtest.pm</source>
+<target dist='default'>home/httpd/lib/perl/Apache/lonplacementtest.pm</target>
+<categoryname>handler</categoryname>
+<description>
+Placement test status and completion.
+</description>
+<status>works/unverified</status>
+</file>
+<file>
 <source>rat/lonpageflip.pm</source>
 <target dist='default'>home/httpd/lib/perl/Apache/lonpageflip.pm</target>
 <categoryname>handler</categoryname>
Index: loncom/enrollment/localenroll.pm
diff -u loncom/enrollment/localenroll.pm:1.53 loncom/enrollment/localenroll.pm:1.54
--- loncom/enrollment/localenroll.pm:1.53	Wed Aug  5 18:47:17 2015
+++ loncom/enrollment/localenroll.pm	Sat Apr  2 04:31:26 2016
@@ -1,6 +1,6 @@
 # functions to glue school database system into Lon-CAPA for 
 # automated enrollment
-# $Id: localenroll.pm,v 1.53 2015/08/05 18:47:17 raeburn Exp $
+# $Id: localenroll.pm,v 1.54 2016/04/02 04:31:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -347,9 +347,10 @@
 the institution.
 
 Course requests will trigger this check if the process type has been set 
-to 'validate' for the course type (official, unofficial or community) and
-the requestor's affiliation.  Whether "validate" is an available option
-in the Domain Configuration menu is controlled by auto_courserequest_checks(). 
+to 'validate' for the course type (official, unofficial, textbook, 
+placement or community) and the requestor's affiliation.  Whether
+"validate" is an available option in the Domain Configuration menu 
+is controlled by auto_courserequest_checks(). 
 One scenario is where the request is for an official course, in which case
 a check could be made that the requestor is listed as instructor of 
 record for the course in the institution's course schedule/database.
@@ -361,7 +362,7 @@
 validate_crsreq takes seven arguments -
  (a) the LON-CAPA domain that will contain the course.
  (b) the username:domain for the course owner.
- (c) the course type (official, unofficial or community)
+ (c) the course type (official, unofficial,textbook, placement or community)
  (d) a comma-separated list of institutional affiliations of 
      the course owner.
  (e) the institutional code (in the MSU case this is a concatenation of
@@ -408,8 +409,9 @@
 possible choices for course request processing in the Domain Configuration 
 menu for Course Requests. Ultimately it is called by domainprefs.pm (via: 
 lonnet -> lond -> localenroll.pm) The domain configuration menu includes 
-a table where columns are course type (official, unofficial or community) 
-and rows are institutional affiliations (e.g., Faculty, Staff, Student etc.).
+a table where columns are course type (official, unofficial, textbook,
+placement or community) and rows are institutional affiliations 
+(e.g., Faculty, Staff, Student etc.).
 
 crsreq_checks() takes three arguments: $dom, $reqtypes, $validations.
 $dom - the domain for which validation options are needed.
Index: loncom/auth/lonroles.pm
diff -u loncom/auth/lonroles.pm:1.312 loncom/auth/lonroles.pm:1.313
--- loncom/auth/lonroles.pm:1.312	Tue Jun  9 21:22:44 2015
+++ loncom/auth/lonroles.pm	Sat Apr  2 04:31:33 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # User Roles Screen
 #
-# $Id: lonroles.pm,v 1.312 2015/06/09 21:22:44 damieng Exp $
+# $Id: lonroles.pm,v 1.313 2016/04/02 04:31:33 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -140,6 +140,7 @@
 use Apache::loncoursequeueadmin;
 use Apache::longroup;
 use Apache::lonrss;
+use Apache::lonplacementtest;
 use GDBM_File;
 use LONCAPA qw(:DEFAULT :match);
 use HTML::Entities;
@@ -588,6 +589,17 @@
 				    $furl = "/adm/helper/course.initialization.helper";
 				    # Send the user to the course they selected
 				} elsif ($env{'request.course.id'}) {
+                                    if ((&Apache::loncommon::course_type() eq 'Placement') && 
+                                        (!$env{'request.role.adv'})) {
+                                        my ($score,$incomplete) = 
+                                            &Apache::lonplacementtest::check_completion(undef,undef,1);
+                                        if (($incomplete) && ($incomplete < 100)) {
+                                            &redirect_user($r, &mt('Entering [_1]',
+                                                          $env{'course.'.$cdom.'_'.$cnum.'.description'}),
+                                                          '/adm/placement', $msg);
+                                            return OK;
+                                        }
+                                    }
                                     my ($dest,$destsymb,$checkenc);
                                     $dest = $env{'form.destinationurl'};
                                     $destsymb = $env{'form.destsymb'};
@@ -740,13 +752,39 @@
     my $showcount = &roles_from_env(\%roles_in_env,$update); 
 
     my $swinfo=&Apache::lonmenu::rawconfig();
-    my $start_page=&Apache::loncommon::start_page($pagetitle,undef,{bread_crumbs=>$brcrum});
     my %domdefs=&Apache::lonnet::get_domain_defaults($env{'user.domain'}); 
     my $cattype = 'std';
     if ($domdefs{'catauth'}) {
         $cattype = $domdefs{'catauth'};
     }
-    my $funcs = &get_roles_functions($showcount,$cattype);
+    my $placementonly;
+    if ($showcount == 1) {
+        if ($env{'request.course.id'}) {
+            if ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') {
+                $placementonly = 1;
+            }
+        } else {
+            foreach my $rolecode (keys(%roles_in_env)) {
+                my ($cid) = ($rolecode =~ m{^\Quser.role.st./\E($match_domain/$match_courseid)(?:/|$)});
+                if ($cid) {
+                    my %coursedescription =
+                        &Apache::lonnet::coursedescription($cid,{'one_time' => '1'});
+                    if ($coursedescription{'type'} eq 'Placement') {
+                        $placementonly = 1;
+                    }
+                    last;
+                }
+            }
+        }
+    }
+    my ($start_page,$funcs);
+    if ($placementonly) {
+        $start_page=&Apache::loncommon::start_page($pagetitle,undef,
+                                                  {bread_crumbs=>$brcrum,crstype=>'Placement'});
+    } else {
+        $start_page=&Apache::loncommon::start_page($pagetitle,undef,{bread_crumbs=>$brcrum}); 
+        $funcs = &get_roles_functions($showcount,$cattype);
+    }
     &js_escape(\$standby);
     my $noscript='<br /><span class="LC_error">'.&mt('Use of LON-CAPA requires Javascript to be enabled in your web browser.').'<br />'.&mt('As this is not the case, most functionality in the system will be unavailable.').'</span><br />';
 
@@ -889,7 +927,21 @@
                       \%sortrole,\%roleclass,\%futureroles,\%timezones,$loncaparev);
     $refresh = $now;
     &Apache::lonnet::appenv({'user.refresh.time'  => $refresh});
-    if ((($cattype eq 'std') || ($cattype eq 'domonly')) && (!$env{'user.adv'})) {
+    if ($countactive == 1) {
+        if ($env{'request.course.id'}) {
+            if ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') {
+                $placementonly = 1;
+            }
+        } elsif ($possiblerole) {
+            if ($possiblerole =~ m{^st\./($match_domain)/($match_courseid)(?:/|$)}) {
+                if ($env{'course.'.$1.'_'.$2.'.type'} eq 'Placement') {
+                    $placementonly = 1;
+                }
+            }
+        }
+    }
+    if ((($cattype eq 'std') || ($cattype eq 'domonly')) && (!$env{'user.adv'}) &&
+          (!$placementonly)) {
         if ($countactive > 0) {
             my $domdesc = &Apache::lonnet::domain($env{'user.domain'},'description');
             my $esc_dom = &HTML::Entities::encode($env{'user.domain'},'"<>&'); 
@@ -942,6 +994,16 @@
         }
         $r->print(&Apache::loncommon::end_page());
 	return OK;
+    } elsif (($placementonly) && ($env{'request.role'} eq 'cm')) {
+	$r->print('<h3>'.&mt('Please stand by.').'</h3>
+	          <input type="hidden" name="'.$possiblerole.'" value="1" />
+                  <noscript><br />
+                  <input type="submit" name="submit" value="'.&mt('Continue').'" />
+                  </noscript></form>');
+	$r->rflush();
+	$r->print('<script type="text/javascript">document.forms.rolechoice.submit();</script>');
+	$r->print(&Apache::loncommon::end_page());
+	return OK;
     }
 # ----------------------------------------------------------------------- Table
 
@@ -1143,6 +1205,7 @@
                 $trole=Apache::lonnet::plaintext($role);
                 my $ttype;
                 my $twhere;
+                my $skipcal;
                 my ($tdom,$trest,$tsection)=
                     split(/\//,Apache::lonnet::declutter($where));
                 # First, Co-Authorship roles
@@ -1232,8 +1295,12 @@
                             $env{'course.'.$tcourseid.'.description'}=$twhere;
                             $sortkey=$role."\0".$tdom."\0".$twhere."\0".$envkey;
                             $ttype = 'Unavailable';
+                            $skipcal = 1;
                         }
                     }
+                    if ($ttype eq 'Placement') {
+                        $ttype = 'Placement Test';
+                    }
                     if ($tsection) {
                         $twhere.='<br />'.&mt('Section').': '.$tsection;
                     }
@@ -1250,7 +1317,8 @@
                 ($role_text,$role_text_end) =
                     &build_roletext($trolecode,$tdom,$trest,$tstatus,$tryagain,
                                     $advanced,$tremark,$tbg,$trole,$twhere,$tpstart,
-                                    $tpend,$nochoose,$button,$switchserver,$reinit,$switchwarning);
+                                    $tpend,$nochoose,$button,$switchserver,$reinit,
+                                    $switchwarning,$skipcal);
                 $roletext->{$envkey}=[$role_text,$role_text_end];
                 if (!$sortkey) {$sortkey=$twhere."\0".$envkey;}
                 $sortrole->{$sortkey}=$envkey;
@@ -1348,7 +1416,7 @@
 }
 
 sub roletypes {
-    my @types = ('Domain','Authoring Space','Course','Community','Unavailable','System');
+    my @types = ('Domain','Authoring Space','Course','Placement Test','Community','Unavailable','System');
     return @types; 
 }
 
@@ -1544,7 +1612,8 @@
 }
 
 sub build_roletext {
-    my ($trolecode,$tdom,$trest,$tstatus,$tryagain,$advanced,$tremark,$tbg,$trole,$twhere,$tpstart,$tpend,$nochoose,$button,$switchserver,$reinit,$switchwarning) = @_;
+    my ($trolecode,$tdom,$trest,$tstatus,$tryagain,$advanced,$tremark,$tbg,$trole,$twhere,
+        $tpstart,$tpend,$nochoose,$button,$switchserver,$reinit,$switchwarning,$skipcal) = @_;
     my ($roletext,$roletext_end);
     my $is_dc=($trolecode =~ m/^dc\./);
     my $rowspan=($is_dc) ? ''
@@ -1600,7 +1669,7 @@
                         $trolecode."','".$buttonname.'\');" /></td>';
         }
     }
-    if ($trolecode !~ m/^(dc|ca|au|aa)\./) {
+    if (($trolecode !~ m/^(dc|ca|au|aa)\./)  && (!$skipcal)) {
 	$tremark.=&Apache::lonannounce::showday(time,1,
 			 &Apache::lonannounce::readcalendar($tdom.'_'.$trest));
     }
@@ -1823,6 +1892,7 @@
             my $trolecode = $ccrole.'./'.$tdom.'/'.$trest;
             my $twhere;
             my $ttype;
+            my $skipcal;
             my $tbg='LC_roles_is';
             my %newhash=&Apache::lonnet::coursedescription($tcourseid);
             if (%newhash) {
@@ -1834,10 +1904,11 @@
             } else {
                 $twhere=&mt('Currently not available');
                 $env{'course.'.$tcourseid.'.description'}=$twhere;
+                $skipcal = 1;
             }
             my $trole = &Apache::lonnet::plaintext($ccrole,$ttype,$tcourseid);
             $twhere.="<br />".&mt('Domain').":".$tdom;
-            ($roletext,$roletext_end) = &build_roletext($trolecode,$tdom,$trest,'is',$tryagain,$advanced,'',$tbg,$trole,$twhere,'','','',1,'');
+            ($roletext,$roletext_end) = &build_roletext($trolecode,$tdom,$trest,'is',$tryagain,$advanced,'',$tbg,$trole,$twhere,'','','',1,'','','',$skipcal);
         }
     }
     return ($roletext,$roletext_end);
Index: loncom/auth/lonauth.pm
diff -u loncom/auth/lonauth.pm:1.139 loncom/auth/lonauth.pm:1.140
--- loncom/auth/lonauth.pm:1.139	Wed Feb 17 19:15:40 2016
+++ loncom/auth/lonauth.pm	Sat Apr  2 04:31:33 2016
@@ -1,7 +1,7 @@
 # The LearningOnline Network
 # User Authentication Module
 #
-# $Id: lonauth.pm,v 1.139 2016/02/17 19:15:40 raeburn Exp $
+# $Id: lonauth.pm,v 1.140 2016/04/02 04:31:33 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -29,7 +29,7 @@
 package Apache::lonauth;
 
 use strict;
-use LONCAPA;
+use LONCAPA qw(:DEFAULT :match);
 use Apache::Constants qw(:common);
 use CGI qw(:standard);
 use DynaLoader; # for Crypt::DES version
@@ -122,9 +122,30 @@
     my $header = '<meta HTTP-EQUIV="Refresh" CONTENT="0; url='.$destination.'" />';
     my $brcrum = [{'href' => '',
                    'text' => 'Successful Login'},];
+    my $args = {'bread_crumbs' => $brcrum,};
+    unless ((defined($form->{role})) || (defined($form->{symb}))) {
+        my $update=$env{'user.update.time'};
+        if (!$update) {
+            $update = $env{'user.login.time'};
+        }
+        my %roles_in_env;
+        my $showcount = &Apache::lonroles::roles_from_env(\%roles_in_env,$update);
+        if ($showcount == 1) {
+            foreach my $rolecode (keys(%roles_in_env)) {
+                my ($cid) = ($rolecode =~ m{^\Quser.role.st./\E($match_domain/$match_courseid)(?:/|$)});
+                if ($cid) {
+                    my %coursedescription =
+                        &Apache::lonnet::coursedescription($cid,{'one_time' => '1'});
+                    if ($coursedescription{'type'} eq 'Placement') {
+                        $args->{'crstype'} = 'Placement';
+                    }
+                    last;
+                }
+            }
+        }
+    }
     my $start_page=&Apache::loncommon::start_page('Successful Login',
-                                                  $header,
-                                                  {'bread_crumbs' => $brcrum,});
+                                                  $header,$args);
     my $end_page  =&Apache::loncommon::end_page();
 
 	my $continuelink='<a href="'.$destination.'">'.&mt('Continue').'</a>';

Index: loncom/interface/lonplacementtest.pm
+++ loncom/interface/lonplacementtest.pm
# The LearningOnline Network with CAPA
# Handler to manage dependencies for HTML files uploaded directly
# to a course.
#
# $Id: lonplacementtest.pm,v 1.1 2016/04/02 04:30:21 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

lonplacementtest - Handler to provide initial screen for student 
after log-in and/or role selection for a Placement Test course container
which is currently partially completed.

=head1 SYNOPSIS

lonplacementtest provides an interface for the student to choose 
whether to continue with an existing, partially completed test,
or whether to start a new test. Also provides utility functions
used to compute/export scores, and increment the course-wide maxtries
parameter for the user, when an instance of a test is complete. 

=head1 DESCRIPTION

This module is used after student log-in and/or role selection
for a Placement Test course contained, if there is a current,
partially completed version of the test.  The student is prompted
to choose whether to continue with the current test or start a
new one.

=head1 INTERNAL SUBROUTINES

=over

=item check_completion()

=back

=cut

package Apache::lonplacementtest;

use strict;
use Apache::Constants qw(:common :http);
use Apache::lonnet;
use Apache::loncommon;
use Apache::lonhtmlcommon;
use Apache::lonnavmaps;
use Apache::lonpageflip;
use Apache::lonroles;
use Apache::lonparmset;
use Apache::lonlocal;
use LONCAPA qw(:DEFAULT :match);

sub handler {
    my $r=shift;
    if ($r->header_only) {
        &Apache::loncommon::content_type($r,'text/html');
        $r->send_http_header;
        return OK;
    }
    if (!$env{'request.course.fn'}) {
        # Not in a course.
        $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a course";
        return HTTP_NOT_ACCEPTABLE;
    } elsif ($env{'course.'.$env{'request.course.id'}.'.type'} ne 'Placement') {
        # Not in a Placement Test
        $env{'user.error.msg'}="/adm/lonplacementtest:bre:0:0:Not in a placement test";
        return HTTP_NOT_ACCEPTABLE;
    }
    &Apache::loncommon::content_type($r,'text/html');
    $r->send_http_header;
    my ($totalpoints,$incomplete) = &check_completion(undef,undef,1);
    if (($incomplete) && ($incomplete < 100) && (!$env{'request.role.adv'})) {
        $r->print(&showincomplete($incomplete));
    } else {
        my $furl = &Apache::lonpageflip::first_accessible_resource();
        my $cdesc = $env{'course.'.$env{'request.course.id'}.'.description'};
        my $msg = &mt('Entering [_1] ...',$cdesc);
        &Apache::lonroles::redirect_user($r, &mt('Entering [_1]',$cdesc),$furl, $msg);
    }
    return OK;
}

sub check_completion {
    my ($makenew,$map,$recursive) = @_;
    my $navmap = Apache::lonnavmaps::navmap->new();
    return unless (ref($navmap));
    my @resources = $navmap->retrieveResources($map,
                                               sub { $_[0]->is_problem() },$recursive);
    my $currmax = 0;
    my $totalpoints = 0;
    my $totaldone = 0;
    my $totalnotdone = 0;
    my $incomplete;
    if (@resources) {
        my $firstsymb = $resources[0]->symb();
        my %bytitle;
        foreach my $res (@resources) {
            my $currsymb = $res->symb();
            my $title = $res->compTitle;
            unless (exists($bytitle{$title})) {
                $bytitle{$title} = 0;
            }
            my $notdone = 0;
            my $done = 0;
            my %storetries;
            my $points = 0;
            foreach my $part (@{$res->parts()}) {
                my $tries = $res->tries($part);
                my $maxtries = $res->maxtries($part);
                if ($currmax < $maxtries) {
                    $currmax = $maxtries;
                }
                if ($tries < $maxtries) {
                    $notdone ++;
                    my $tries = $res->tries($part);
                    my @response_ids = $res->responseIds($part);
                    if (@response_ids) {
                        foreach my $id (@response_ids) {
                            $storetries{"resource.$part.$id.awarded"}=0;
                            $storetries{"resource.$part.$id.awarddetail"}='ASSIGNED_SCORE';
                        }
                        $storetries{"resource.$part.tries"}=$maxtries;
                        $storetries{"resource.$part.solved"}='incorrect_by_override';
                        $storetries{"resource.$part.award"}='ASSIGNED_SCORE';
                        $storetries{"resource.$part.awarded"}=0;
                    }
                } else {
                    my $awarded = $res->awarded($part);
                    my $weight = $res->weight($part);
                    $points += $awarded * $weight;
                    $done ++;
                }
            }
            if ($notdone) {
                $totalnotdone += $notdone;
                if ($makenew && keys(%storetries)) {
                    my $result=&Apache::lonnet::cstore(\%storetries,$currsymb,$env{'request.course.id'},
                                                       $env{'user.domain'},$env{'user.name'});
                }
            }
            if ($done) {
                $totaldone += $done;
            }
            $bytitle{$title} += $points;
            $totalpoints += $points;
        }
        if ($makenew) {
            my $newmax = $currmax + 1;
            my $result =
                &Apache::lonparmset::storeparm_by_symb_inner($firstsymb,'0_maxtries',
                                                             4,$newmax,'int_pos',
                                                             $env{'user.name'},
                                                             $env{'user.domain'});
            my %grades = (
                             uname   => $env{'user.name'},
                             domain  => $env{'user.domain'},
                             total   => $totalpoints,
                             bytitle => \%bytitle,
                         );
            my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
            my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
            if (($cnum ne '') && ($cdom ne '')) {
                &Apache::lonnet::auto_export_grades($cnum,$cdom,\%grades);
            }
        }
    }
    my $totalparts = $totalnotdone + $totaldone;
    if (($totalparts) && ($totalnotdone)) {
        if (!$totaldone) {
            $incomplete = 100;
        } else {
            my $undonepct = (100*$totalnotdone)/$totalparts;
            $incomplete = sprintf("%0d",$undonepct);
        }
    }
    return ($totalpoints,$incomplete);
}

sub showresult {
    my ($complete) = @_;
    my ($score) = &Apache::lonplacementtest::check_completion(1,undef,1);
    my %aclt = &test_action_text();
    my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a',
                   'text' => 'Test Status'},];
    my $output = &Apache::loncommon::start_page('Placement Test Completed',
                                          undef,{bread_crumbs=>$brcrum});
    if ($complete) {
        $output .= '<p class="LC_info">'.&mt('Test is complete').'</p>';
    }
    return $output
          .'<p>'.&mt('You scored [quant,_1,point].',$score).'</p>'
          .&Apache::lonhtmlcommon::actionbox(
              ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'newt'}.'</a></li>',
               '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
              ])
          .&Apache::loncommon::end_page();
}

sub showincomplete {
    my ($incomplete) = @_;
    my %aclt = &test_action_text();
    if ($incomplete == 100) {
        my $brcrum = [{'href' => '/adm/flip?postdata=firstres%3a',
                       'text' => 'Test Status'},];
        return &Apache::loncommon::start_page('Placement Test Unattempted',
                                              undef,{bread_crumbs=>$brcrum})
              .'<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
              .&mt('Currently, you have not submitted any answers for any of the questions.')
              .'</p>'
              .&Apache::lonhtmlcommon::actionbox(
                  ['<a href="/adm/flip?postdata=firstres%3a">'.$aclt{'begin'}.'</a></li>',
                   '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
                  ])
              .&Apache::loncommon::end_page(); 
    } elsif ($incomplete) {
        my $brcrum = [{'href' => '/adm/flip?postdata=endplacement%3a',
                       'text' => 'Test Status'},];
        return &Apache::loncommon::start_page('Incomplete Placement Test',
                                              undef,{bread_crumbs=>$brcrum})  
              .'<p class="LC_warning">'.&mt('Your Placement Test is incomplete.').'<p></p>'
              .&mt('Currently, you have not provided an answer for [_1]% of the questions.',$incomplete)
              .'</p>'
              .&Apache::lonhtmlcommon::actionbox(
                  ['<a href="/adm/flip?postdata=endplacement%3a">'.$aclt{'endt'}.'</a></li>',
                   '<a href="/adm/flip?postdata=firstanswerable%3a">'.$aclt{'comp'}.'</a></li>',
                   '<a href="/adm/logout">'.$aclt{'exit'}.'</a></li>',
                  ])
              .&Apache::loncommon::end_page();
    }
}

sub test_action_text {
    return &Apache::lonlocal::texthash(
                                        'exit'  => 'Logout',
                                        'newt'  => 'Start a new test',
                                        'endt'  => 'Mark test as completed',
                                        'comp'  => 'Go to first unanswered question',
                                        'begin' => 'Go to start',
                                      );
}

1;


More information about the LON-CAPA-cvs mailing list