--- loncom/interface/loncommon.pm	2014/06/18 06:06:50	1.1194
+++ loncom/interface/loncommon.pm	2016/07/18 19:28:58	1.1250
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1194 2014/06/18 06:06:50 raeburn Exp $
+# $Id: loncommon.pm,v 1.1250 2016/07/18 19:28:58 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -72,12 +72,17 @@ use Apache::lonuserstate();
 use Apache::courseclassifier();
 use LONCAPA qw(:DEFAULT :match);
 use DateTime::TimeZone;
-use DateTime::Locale::Catalog;
+use DateTime::Locale;
+use Encode();
 use Text::Aspell;
 use Authen::Captcha;
 use Captcha::reCAPTCHA;
+use JSON::DWIW;
+use LWP::UserAgent;
 use Crypt::DES;
 use DynaLoader; # for Crypt::DES version
+use MIME::Lite;
+use MIME::Types;
 
 # ---------------------------------------------- Designs
 use vars qw(%defaultdesign);
@@ -535,7 +540,7 @@ ENDAUTHORBRW
 
 sub coursebrowser_javascript {
     my ($domainfilter,$sec_element,$formname,$role_element,$crstype,
-        $credits_element) = @_;
+        $credits_element,$instcode) = @_;
     my $wintitle = 'Course_Browser';
     if ($crstype eq 'Community') {
         $wintitle = 'Community_Browser';
@@ -586,6 +591,12 @@ sub coursebrowser_javascript {
             var ownername = document.forms[formid].ccuname.value;
             var ownerdom =  document.forms[formid].ccdomain.options[document.forms[formid].ccdomain.selectedIndex].value;
             url += '&cloner='+ownername+':'+ownerdom;
+            if (type == 'Course') {
+                url += '&crscode='+document.forms[formid].crscode.value;
+            }
+        }
+        if (formname == 'requestcrs') {
+            url += '&crsdom=$domainfilter&crscode=$instcode';
         }
         if (multflag !=null && multflag != '') {
             url += '&multiple='+multflag;
@@ -870,6 +881,8 @@ sub selectcourse_link {
    my $linktext = &mt('Select Course');
    if ($selecttype eq 'Community') {
        $linktext = &mt('Select Community');
+   } elsif ($selecttype eq 'Placement') {
+       $linktext = &mt('Select Placement Test'); 
    } elsif ($selecttype eq 'Course/Community') {
        $linktext = &mt('Select Course/Community');
        $type = '';
@@ -961,15 +974,16 @@ sub select_datelocale {
         }
         $output .= '> </option>';
     }
+    my @languages = &Apache::lonlocal::preferred_languages();
     my (@possibles,%locale_names);
-    my @locales = DateTime::Locale::Catalog::Locales;
-    foreach my $locale (@locales) {
-        if (ref($locale) eq 'HASH') {
-            my $id = $locale->{'id'};
-            if ($id ne '') {
-                my $en_terr = $locale->{'en_territory'};
-                my $native_terr = $locale->{'native_territory'};
-                my @languages = &Apache::lonlocal::preferred_languages();
+    my @locales = DateTime::Locale->ids();
+    foreach my $id (@locales) {
+        if ($id ne '') {
+            my ($en_terr,$native_terr);
+            my $loc = DateTime::Locale->load($id);
+            if (ref($loc)) {
+                $en_terr = $loc->name();
+                $native_terr = $loc->native_name();
                 if (grep(/^en$/,@languages) || !@languages) {
                     if ($en_terr ne '') {
                         $locale_names{$id} = '('.$en_terr.')';
@@ -983,8 +997,9 @@ sub select_datelocale {
                         $locale_names{$id} = '('.$en_terr.')';
                     }
                 }
-                push (@possibles,$id);
-            }
+                $locale_names{$id} = Encode::encode('UTF-8',$locale_names{$id});
+                push(@possibles,$id);
+            } 
         }
     }
     foreach my $item (sort(@possibles)) {
@@ -994,7 +1009,7 @@ sub select_datelocale {
         }
         $output.=">$item";
         if ($locale_names{$item} ne '') {
-            $output.="  $locale_names{$item}</option>\n";
+            $output.='  '.$locale_names{$item};
         }
         $output.="</option>\n";
     }
@@ -1079,6 +1094,9 @@ linked_select_forms takes the following
 =item * $onchangesecond, additional javascript call to execute for an onchange
         event for the second <select> tag
 
+=item * $suffix, to differentiate separate uses of select2data javascript
+        objects in a page.
+
 =back 
 
 Below is an example of such a hash.  Only the 'text', 'default', and 
@@ -1133,7 +1151,8 @@ sub linked_select_forms {
         $hashref,
         $menuorder,
         $onchangefirst,
-        $onchangesecond
+        $onchangesecond,
+        $suffix
         ) = @_;
     my $second = "document.$formname.$secondselectname";
     my $first = "document.$formname.$firstselectname";
@@ -1141,20 +1160,20 @@ sub linked_select_forms {
     my $result = '';
     $result.='<script type="text/javascript" language="JavaScript">'."\n";
     $result.="// <![CDATA[\n";
-    $result.="var select2data = new Object();\n";
+    $result.="var select2data${suffix} = new Object();\n";
     $" = '","';
     my $debug = '';
     foreach my $s1 (sort(keys(%$hashref))) {
-        $result.="select2data.d_$s1 = new Object();\n";        
-        $result.="select2data.d_$s1.def = new String('".
+        $result.="select2data${suffix}['d_$s1'] = new Object();\n";        
+        $result.="select2data${suffix}['d_$s1'].def = new String('".
             $hashref->{$s1}->{'default'}."');\n";
-        $result.="select2data.d_$s1.values = new Array(";
+        $result.="select2data${suffix}['d_$s1'].values = new Array(";
         my @s2values = sort(keys( %{ $hashref->{$s1}->{'select2'} } ));
         if (ref($hashref->{$s1}->{'order'}) eq 'ARRAY') {
             @s2values = @{$hashref->{$s1}->{'order'}};
         }
         $result.="\"@s2values\");\n";
-        $result.="select2data.d_$s1.texts = new Array(";        
+        $result.="select2data${suffix}['d_$s1'].texts = new Array(";        
         my @s2texts;
         foreach my $value (@s2values) {
             push @s2texts, $hashref->{$s1}->{'select2'}->{$value};
@@ -1164,19 +1183,17 @@ sub linked_select_forms {
     $"=' ';
     $result.= <<"END";
 
-function select1_changed() {
+function select1${suffix}_changed() {
     // Determine new choice
-    var newvalue = "d_" + $first.value;
+    var newvalue = "d_" + $first.options[$first.selectedIndex].value;
     // update select2
-    var values     = select2data[newvalue].values;
-    var texts      = select2data[newvalue].texts;
-    var select2def = select2data[newvalue].def;
+    var values     = select2data${suffix}[newvalue].values;
+    var texts      = select2data${suffix}[newvalue].texts;
+    var select2def = select2data${suffix}[newvalue].def;
     var i;
     // out with the old
-    for (i = 0; i < $second.options.length; i++) {
-        $second.options[i] = null;
-    }
-    // in with the nuclear
+    $second.options.length = 0;
+    // in with the new
     for (i=0;i<values.length; i++) {
         $second.options[i] = new Option(values[i]);
         $second.options[i].value = values[i];
@@ -1190,7 +1207,7 @@ function select1_changed() {
 </script>
 END
     # output the initial values for the selection lists
-    $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1_changed();$onchangefirst\">\n";
+    $result .= "<select size=\"1\" name=\"$firstselectname\" onchange=\"select1${suffix}_changed();$onchangefirst\">\n";
     my @order = sort(keys(%{$hashref}));
     if (ref($menuorder) eq 'ARRAY') {
         @order = @{$menuorder};
@@ -1757,6 +1774,521 @@ RESIZE
 
 }
 
+sub colorfuleditor_js {
+    my $browse_or_search;
+    my $respath;
+    my ($cnum,$cdom) = &crsauthor_url();
+    if ($cnum) {
+        $respath = "/res/$cdom/$cnum/";
+        my %js_lt = &Apache::lonlocal::texthash(
+            sunm => 'Sub-directory name',
+            save => 'Save page to make this permanent',
+        );
+        &js_escape(\%js_lt);
+        $browse_or_search = <<"END";
+
+    function toggleChooser(form,element,titleid,only,search) {
+        var disp = 'none';
+        if (document.getElementById('chooser_'+element)) {
+            var curr = document.getElementById('chooser_'+element).style.display;
+            if (curr == 'none') {
+                disp='inline';
+                if (form.elements['chooser_'+element].length) {
+                    for (var i=0; i<form.elements['chooser_'+element].length; i++) {
+                        form.elements['chooser_'+element][i].checked = false;
+                    }
+                }
+                toggleResImport(form,element);
+            }
+            document.getElementById('chooser_'+element).style.display = disp;
+        }
+    }
+
+    function toggleCrsFile(form,element,numdirs) {
+        if (document.getElementById('chooser_'+element+'_crsres')) {
+            var curr = document.getElementById('chooser_'+element+'_crsres').style.display;
+            if (curr == 'none') {
+                if (numdirs) {
+                    form.elements['coursepath_'+element].selectedIndex = 0;
+                    if (numdirs > 1) {
+                        window['select1'+element+'_changed']();
+                    }
+                }
+            } 
+            document.getElementById('chooser_'+element+'_crsres').style.display = 'block';
+            
+        }
+        if (document.getElementById('chooser_'+element+'_upload')) {
+            document.getElementById('chooser_'+element+'_upload').style.display = 'none';
+            if (document.getElementById('uploadcrsres_'+element)) {
+                document.getElementById('uploadcrsres_'+element).value = '';
+            }
+        }
+        return;
+    }
+
+    function toggleCrsUpload(form,element,numcrsdirs) {
+        if (document.getElementById('chooser_'+element+'_crsres')) {
+            document.getElementById('chooser_'+element+'_crsres').style.display = 'none';
+        }
+        if (document.getElementById('chooser_'+element+'_upload')) {
+            var curr = document.getElementById('chooser_'+element+'_upload').style.display;
+            if (curr == 'none') {
+                if (numcrsdirs) {
+                   form.elements['crsauthorpath_'+element].selectedIndex = 0;
+                   form.elements['newsubdir_'+element][0].checked = true;
+                   toggleNewsubdir(form,element);
+                }
+            }
+            document.getElementById('chooser_'+element+'_upload').style.display = 'block';
+        }
+        return;
+    }
+
+    function toggleResImport(form,element) {
+        var choices = new Array('crsres','upload');
+        for (var i=0; i<choices.length; i++) {
+            if (document.getElementById('chooser_'+element+'_'+choices[i])) {
+                document.getElementById('chooser_'+element+'_'+choices[i]).style.display = 'none';
+            }
+        }
+    }
+
+    function toggleNewsubdir(form,element) {
+        var newsub = form.elements['newsubdir_'+element];
+        if (newsub) {
+            if (newsub.length) {
+                for (var j=0; j<newsub.length; j++) {
+                    if (newsub[j].checked) {
+                        if (document.getElementById('newsubdirname_'+element)) {
+                            if (newsub[j].value == '1') {
+                                document.getElementById('newsubdirname_'+element).type = "text";
+                                if (document.getElementById('newsubdir_'+element)) {
+                                    document.getElementById('newsubdir_'+element).innerHTML = '<br />$js_lt{sunm}';
+                                }
+                            } else {
+                                document.getElementById('newsubdirname_'+element).type = "hidden";
+                                document.getElementById('newsubdirname_'+element).value = "";
+                                document.getElementById('newsubdir_'+element).innerHTML = "";
+                            }
+                        }
+                        break; 
+                    }
+                }
+            }
+        }
+    }
+
+    function updateCrsFile(form,element) {
+        var directory = form.elements['coursepath_'+element];
+        var filename = form.elements['coursefile_'+element];
+        var path = directory.options[directory.selectedIndex].value;
+        var file = filename.options[filename.selectedIndex].value;
+        form.elements[element].value = '$respath';
+        if (path == '/') {
+            form.elements[element].value += file;
+        } else {
+            form.elements[element].value += path+'/'+file;
+        }
+        unClean();
+        if (document.getElementById('previewimg_'+element)) {
+            document.getElementById('previewimg_'+element).src = form.elements[element].value;
+            var newsrc = document.getElementById('previewimg_'+element).src; 
+        }
+        if (document.getElementById('showimg_'+element)) {
+            document.getElementById('showimg_'+element).innerHTML = '($js_lt{save})';
+        }
+        toggleChooser(form,element);
+        return;
+    }
+
+    function uploadDone(suffix,name) {
+        if (name) {
+	    document.forms["lonhomework"].elements[suffix].value = name;
+            unClean();
+            toggleChooser(document.forms["lonhomework"],suffix);
+        }
+    }
+
+\$(document).ready(function(){
+
+    \$(document).delegate('form :submit', 'click', function( event ) {
+        if ( \$( this ).hasClass( "LC_uploadcrsres" ) ) {
+            var buttonId = this.id;
+            var suffix = buttonId.toString();
+            suffix = suffix.replace(/^crsupload_/,'');
+            event.preventDefault();
+            document.lonhomework.target = 'crsupload_target_'+suffix;
+            document.lonhomework.action = '/adm/coursepub?LC_uploadcrsres='+suffix;
+            \$(this.form).submit();
+            document.lonhomework.target = '';
+            if (document.getElementById('crsuploadto_'+suffix)) {
+                document.lonhomework.action = document.getElementById('crsuploadto_'+suffix).value;
+            }
+            return false;
+        }
+    });
+});
+END
+    }
+    return <<"COLORFULEDIT"
+<script type="text/javascript">
+// <![CDATA[>
+    function fold_box(curDepth, lastresource){
+
+    // we need a list because there can be several blocks you need to fold in one tag
+        var block = document.getElementsByName('foldblock_'+curDepth);
+    // but there is only one folding button per tag
+        var foldbutton = document.getElementById('folding_btn_'+curDepth);
+
+        if(block.item(0).style.display == 'none'){
+
+            foldbutton.value = '@{[&mt("Hide")]}';
+            for (i = 0; i < block.length; i++){
+                block.item(i).style.display = '';
+            }
+        }else{
+
+            foldbutton.value = '@{[&mt("Show")]}';
+            for (i = 0; i < block.length; i++){
+                // block.item(i).style.visibility = 'collapse';
+                block.item(i).style.display = 'none';
+            }
+        };
+        saveState(lastresource);
+    }
+
+    function saveState (lastresource) {
+
+        var tag_list = getTagList();
+        if(tag_list != null){
+            var timestamp = new Date().getTime();
+            var key = lastresource;
+
+            // the value pattern is: 'time;key1,value1;key2,value2; ... '
+            // starting with timestamp
+            var value = timestamp+';';
+
+            // building the list of key-value pairs
+            for(var i = 0; i < tag_list.length; i++){
+                value += tag_list[i]+',';
+                value += document.getElementsByName(tag_list[i])[0].style.display+';';
+            }
+
+            // only iterate whole storage if nothing to override
+            if(localStorage.getItem(key) == null){        
+
+                // prevent storage from growing large
+                if(localStorage.length > 50){
+                    var regex_getTimestamp = /^(?:\d)+;/;
+                    var oldest_timestamp = regex_getTimestamp.exec(localStorage.key(0));
+                    var oldest_key;
+                    
+                    for(var i = 1; i < localStorage.length; i++){
+                        if (regex_getTimestamp.exec(localStorage.key(i)) < oldest_timestamp) {
+                            oldest_key = localStorage.key(i);
+                            oldest_timestamp = regex_getTimestamp.exec(oldest_key);
+                        }
+                    }
+                    localStorage.removeItem(oldest_key);
+                }
+            }
+            localStorage.setItem(key,value);
+        }
+    }
+
+    // restore folding status of blocks (on page load)
+    function restoreState (lastresource) {
+        if(localStorage.getItem(lastresource) != null){
+            var key = lastresource;
+            var value = localStorage.getItem(key);
+            var regex_delTimestamp = /^\d+;/;
+
+            value.replace(regex_delTimestamp, '');
+
+            var valueArr = value.split(';');
+            var pairs;
+            var elements;
+            for (var i = 0; i < valueArr.length; i++){
+                pairs = valueArr[i].split(',');
+                elements = document.getElementsByName(pairs[0]);
+
+                for (var j = 0; j < elements.length; j++){  
+                    elements[j].style.display = pairs[1];
+                    if (pairs[1] == "none"){
+                        var regex_id = /([_\\d]+)\$/;
+                        regex_id.exec(pairs[0]);
+                        document.getElementById("folding_btn"+RegExp.\$1).value = "Show";
+                    }
+                }
+            }
+        }
+    }
+
+    function getTagList () {
+        
+        var stringToSearch = document.lonhomework.innerHTML;
+
+        var ret = new Array();
+        var regex_findBlock = /(foldblock_.*?)"/g;
+        var tag_list = stringToSearch.match(regex_findBlock);
+
+        if(tag_list != null){
+            for(var i = 0; i < tag_list.length; i++){            
+                ret.push(tag_list[i].replace(/"/, ''));
+            }
+        }
+        return ret;
+    }
+
+    function saveScrollPosition (resource) {
+        var tag_list = getTagList();
+
+        // we dont always want to jump to the first block
+        // 170 is roughly above the "Problem Editing" header. we just want to save if the user scrolled down further than this
+        if(\$(window).scrollTop() > 170){
+            if(tag_list != null){
+                var result;
+                for(var i = 0; i < tag_list.length; i++){
+                    if(isElementInViewport(tag_list[i])){
+                        result += tag_list[i]+';';
+                    }
+                }
+                sessionStorage.setItem('anchor_'+resource, result);
+            }
+        } else {
+            // we dont need to save zero, just delete the item to leave everything tidy
+            sessionStorage.removeItem('anchor_'+resource);
+        }
+    }
+
+    function restoreScrollPosition(resource){
+
+        var elem = sessionStorage.getItem('anchor_'+resource);
+        if(elem != null){
+            var tag_list = elem.split(';');
+            var elem_list;
+
+            for(var i = 0; i < tag_list.length; i++){
+                elem_list = document.getElementsByName(tag_list[i]);
+                
+                if(elem_list.length > 0){
+                    elem = elem_list[0];
+                    break;
+                }
+            }
+            elem.scrollIntoView();
+        }
+    }
+
+    function isElementInViewport(el) {
+
+        // change to last element instead of first
+        var elem = document.getElementsByName(el);
+        var rect = elem[0].getBoundingClientRect();
+
+        return (
+            rect.top >= 0 &&
+            rect.left >= 0 &&
+            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
+            rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
+        );
+    }
+    
+    function autosize(depth){
+        var cmInst = window['cm'+depth];
+        var fitsizeButton = document.getElementById('fitsize'+depth);
+
+        // is fixed size, switching to dynamic
+        if (sessionStorage.getItem("autosized_"+depth) == null) {
+            cmInst.setSize("","auto");
+            fitsizeButton.value = "@{[&mt('Fixed size')]}";
+            sessionStorage.setItem("autosized_"+depth, "yes");
+
+        // is dynamic size, switching to fixed
+        } else {
+            cmInst.setSize("","300px");
+            fitsizeButton.value = "@{[&mt('Dynamic size')]}";
+            sessionStorage.removeItem("autosized_"+depth);
+        }
+    }
+
+$browse_or_search
+
+// ]]>
+</script>
+COLORFULEDIT
+}
+
+sub xmleditor_js {
+    return <<XMLEDIT
+<script type="text/javascript" src="/adm/jQuery/addons/jquery-scrolltofixed.js"></script>
+<script type="text/javascript">
+// <![CDATA[>
+
+    function saveScrollPosition (resource) {
+
+        var scrollPos = \$(window).scrollTop();
+        sessionStorage.setItem(resource,scrollPos);
+    }
+
+    function restoreScrollPosition(resource){
+
+        var scrollPos = sessionStorage.getItem(resource);
+        \$(window).scrollTop(scrollPos);
+    }
+
+    // unless internet explorer
+    if (!(window.navigator.appName == "Microsoft Internet Explorer" && (document.documentMode || document.compatMode))){
+
+        \$(document).ready(function() {
+             \$(".LC_edit_actionbar").scrollToFixed(\{zIndex: 100\});
+        });
+    }
+
+    // inserts text at cursor position into codemirror (xml editor only)
+    function insertText(text){
+        cm.focus();
+        var curPos = cm.getCursor();
+        cm.replaceRange(text.replace(/ESCAPEDSCRIPT/g,'script'), {line: curPos.line,ch: curPos.ch});
+    }
+// ]]>
+</script>
+XMLEDIT
+}
+
+sub insert_folding_button {
+    my $curDepth = $Apache::lonxml::curdepth;
+    my $lastresource = $env{'request.ambiguous'};
+
+    return "<input type=\"button\" id=\"folding_btn_$curDepth\" 
+            value=\"".&mt('Hide')."\" onclick=\"fold_box('$curDepth','$lastresource')\">";
+}
+
+sub crsauthor_url {
+    my ($url) = @_;
+    if ($url eq '') {
+        $url = $ENV{'REQUEST_URI'};
+    }
+    my ($cnum,$cdom);
+    if ($env{'request.course.id'}) {
+        my ($audom,$auname) = ($url =~ m{^/priv/($match_domain)/($match_name)/});
+        if ($audom ne '' && $auname ne '') {
+            if (($env{'course.'.$env{'request.course.id'}.'.num'} eq $auname) &&
+                ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $audom)) {
+                $cnum = $auname;
+                $cdom = $audom;
+            }
+        }
+    }
+    return ($cnum,$cdom);
+}
+
+sub import_crsauthor_form {
+    my ($form,$firstselectname,$secondselectname,$onchangefirst,$only,$suffix) = @_;
+    return (0) unless ($env{'request.course.id'});
+    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+    my $crshome = $env{'course.'.$env{'request.course.id'}.'.home'};
+    return (0) unless (($cnum ne '') && ($cdom ne ''));
+    my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'};
+    my @ids=&Apache::lonnet::current_machine_ids();
+    my ($output,$is_home,$relpath,%subdirs,%files,%selimport_menus);
+    
+    if (grep(/^\Q$crshome\E$/,@ids)) {
+        $is_home = 1;
+    }
+    $relpath = "/priv/$cdom/$cnum";
+    &Apache::lonnet::recursedirs($is_home,'priv',$londocroot,$relpath,'',\%subdirs,\%files);
+    my %lt = &Apache::lonlocal::texthash (
+        fnam => 'Filename',
+        dire => 'Directory',
+    );
+    my $numdirs = scalar(keys(%files));
+    my (%possexts,$singledir,@singledirfiles);
+    if ($only) {
+        map { $possexts{$_} = 1; } split(/\s*,\s*/,$only);
+    }
+    my (%nonemptydirs,$possdirs);
+    if ($numdirs > 1) {
+        my @order;
+        foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) {
+            if (ref($files{$key}) eq 'HASH') {
+                my $shown = $key;
+                if ($key eq '') {
+                    $shown = '/';
+                }
+                my @ordered = ();
+                foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$key}}))) {
+                    if ($only) {
+                        my ($ext) = ($file =~ /\.([^.]+)$/);
+                        unless ($possexts{lc($ext)}) {
+                            next;
+                        }
+                    }
+                    $selimport_menus{$key}->{'select2'}->{$file} = $file;
+                    push(@ordered,$file);
+                }
+                if (@ordered) {
+                    push(@order,$key);
+                    $nonemptydirs{$key} = 1;
+                    $selimport_menus{$key}->{'text'} = $shown;
+                    $selimport_menus{$key}->{'default'} = '';
+                    $selimport_menus{$key}->{'select2'}->{''} = '';
+                    $selimport_menus{$key}->{'order'} = \@ordered;
+                }
+            }
+        }
+        $possdirs = scalar(keys(%nonemptydirs));
+        if ($possdirs > 1) {
+            my @order = sort { lc($a) cmp lc($b) } (keys(%nonemptydirs));
+            $output = $lt{'dire'}.
+                      &linked_select_forms($form,'<br />'.
+                                           $lt{'fnam'},'',
+                                           $firstselectname,$secondselectname,
+                                           \%selimport_menus,\@order,
+                                           $onchangefirst,'',$suffix).'<br />';
+        } elsif ($possdirs == 1) {
+            $singledir = (keys(%nonemptydirs))[0];
+            if (ref($selimport_menus{$singledir}->{'order'}) eq 'ARRAY') {
+                @singledirfiles = @{$selimport_menus{$singledir}->{'order'}};
+            }
+            delete($selimport_menus{$singledir});
+        }
+    } elsif ($numdirs == 1) {
+        $singledir = (keys(%files))[0];
+        foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$singledir}}))) {
+            if ($only) {
+                my ($ext) = ($file =~ /\.([^.]+)$/);
+                unless ($possexts{lc($ext)}) {
+                    next;
+                }
+            }
+            push(@singledirfiles,$file);
+        }
+        if (@singledirfiles) {
+            $possdirs == 1;
+        }
+    }
+    if (($possdirs == 1) && (@singledirfiles)) {
+        my $showdir = $singledir;
+        if ($singledir eq '') {
+            $showdir = '/';
+        }
+        $output = $lt{'dire'}.
+                  '<select name="'.$firstselectname.'">'.
+                  '<option value="'.$singledir.'">'.$showdir.'</option>'."\n".
+                  '</select><br />'.
+                  $lt{'fnam'}.'<select name="'.$secondselectname.'">'."\n".
+                  '<option value="" selected="selected">'.$lt{'se'}.'</option>'."\n";
+        foreach my $file (@singledirfiles) {
+            $output .= '<option value="'.$file.'">'.$file.'</option>'."\n";
+        }
+        $output .= '</select><br />'."\n";
+    }
+    return ($possdirs,$output);
+}
+
 =pod
 
 =head1 Excel and CSV file utility routines
@@ -2029,12 +2561,16 @@ See lonrights.pm for an example invocati
 
 #-------------------------------------------
 sub select_form {
-    my ($def,$name,$hashref,$onchange) = @_;
+    my ($def,$name,$hashref,$onchange,$readonly) = @_;
     return unless (ref($hashref) eq 'HASH');
     if ($onchange) {
         $onchange = ' onchange="'.$onchange.'"';
     }
-    my $selectform = "<select name=\"$name\" size=\"1\"$onchange>\n";
+    my $disabled;
+    if ($readonly) {
+        $disabled = ' disabled="disabled"';
+    }
+    my $selectform = "<select name=\"$name\" size=\"1\"$onchange$disabled>\n";
     my @keys;
     if (exists($hashref->{'select_form_order'})) {
 	@keys=@{$hashref->{'select_form_order'}};
@@ -3618,7 +4154,11 @@ category
 
 sub filecategorytypes {
     my ($cat) = @_;
-    return @{$category_extensions{lc($cat)}};
+    if (ref($category_extensions{lc($cat)}) eq 'ARRAY') { 
+        return @{$category_extensions{lc($cat)}};
+    } else {
+        return ();
+    }
 }
 
 =pod
@@ -3761,7 +4301,7 @@ sub user_lang {
 =over 4
 
 =item * &get_previous_attempt($symb, $username, $domain, $course,
-    $getattempt, $regexp, $gradesub)
+    $getattempt, $regexp, $gradesub, $usec, $identifier)
 
 Return string with previous attempt on problem. Arguments:
 
@@ -3783,6 +4323,11 @@ Return string with previous attempt on p
 
 =item * $gradesub: routine that processes the string if it matches $regexp
 
+=item * $usec: section of the desired student
+
+=item * $identifier: counter for student (multiple students one problem) or 
+    problem (one student; whole sequence).
+
 =back
 
 The output string is a table containing all desired attempts, if any.
@@ -3790,7 +4335,7 @@ The output string is a table containing
 =cut
 
 sub get_previous_attempt {
-  my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_;
+  my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub,$usec,$identifier)=@_;
   my $prevattempts='';
   no strict 'refs';
   if ($symb) {
@@ -3800,13 +4345,18 @@ sub get_previous_attempt {
       my %lasthash=();
       my $version;
       for ($version=1;$version<=$returnhash{'version'};$version++) {
-        foreach my $key (sort(split(/\:/,$returnhash{$version.':keys'}))) {
-	  $lasthash{$key}=$returnhash{$version.':'.$key};
+        foreach my $key (reverse(sort(split(/\:/,$returnhash{$version.':keys'})))) {
+            if ($key =~ /\.rawrndseed$/) {
+                my ($id) = ($key =~ /^(.+)\.rawrndseed$/);
+                $lasthash{$id.'.rndseed'} = $returnhash{$version.':'.$key};
+            } else {
+                $lasthash{$key}=$returnhash{$version.':'.$key};
+            }
         }
       }
       $prevattempts=&start_data_table().&start_data_table_header_row();
       $prevattempts.='<th>'.&mt('History').'</th>';
-      my (%typeparts,%lasthidden);
+      my (%typeparts,%lasthidden,%regraded,%hidestatus);
       my $showsurv=&Apache::lonnet::allowed('vas',$env{'request.course.id'});
       foreach my $key (sort(keys(%lasthash))) {
 	my ($ign,@parts) = split(/\./,$key);
@@ -3823,6 +4373,18 @@ sub get_previous_attempt {
                       $lasthidden{$ign.'.'.$id} = 1;
                   }
               }
+              if ($identifier ne '') {
+                  my $id = join(',',@parts);
+                  if (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb,
+                                               $domain,$username,$usec,undef,$course) =~ /^no/) {
+                      $hidestatus{$ign.'.'.$id} = 1;
+                  }
+              }
+          } elsif ($data eq 'regrader') {
+              if (($identifier ne '') && (@parts)) {
+                  my $id = join(',',@parts);
+                  $regraded{$ign.'.'.$id} = 1;
+              }
           } 
 	} else {
 	  if ($#parts == 0) {
@@ -3834,17 +4396,60 @@ sub get_previous_attempt {
       }
       $prevattempts.=&end_data_table_header_row();
       if ($getattempt eq '') {
+        my (%solved,%resets,%probstatus);
+        if (($identifier ne '') && (keys(%regraded) > 0)) {
+            for ($version=1;$version<=$returnhash{'version'};$version++) {
+                foreach my $id (keys(%regraded)) {
+                    if (($returnhash{$version.':'.$id.'.regrader'}) &&
+                        ($returnhash{$version.':'.$id.'.tries'} eq '') &&
+                        ($returnhash{$version.':'.$id.'.award'} eq '')) {
+                        push(@{$resets{$id}},$version);
+                    }
+                }
+            }
+        }
 	for ($version=1;$version<=$returnhash{'version'};$version++) {
-            my @hidden;
+            my (@hidden,@unsolved);
             if (%typeparts) {
                 foreach my $id (keys(%typeparts)) {
-                    if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
+                    if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || 
+                        ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
                         push(@hidden,$id);
+                    } elsif ($identifier ne '') {
+                        unless (($returnhash{$version.':'.$id.'.type'} eq 'survey') ||
+                                ($returnhash{$version.':'.$id.'.type'} eq 'surveycred') ||
+                                ($hidestatus{$id})) {
+                            next if ((ref($resets{$id}) eq 'ARRAY') && grep(/^\Q$version\E$/,@{$resets{$id}}));
+                            if ($returnhash{$version.':'.$id.'.solved'} eq 'correct_by_student') {
+                                push(@{$solved{$id}},$version);
+                            } elsif (($returnhash{$version.':'.$id.'.solved'} ne '') &&
+                                     (ref($solved{$id}) eq 'ARRAY')) {
+                                my $skip;
+                                if (ref($resets{$id}) eq 'ARRAY') {
+                                    foreach my $reset (@{$resets{$id}}) {
+                                        if ($reset > $solved{$id}[-1]) {
+                                            $skip=1;
+                                            last;
+                                        }
+                                    }
+                                }
+                                unless ($skip) {
+                                    my ($ign,$partslist) = split(/\./,$id,2);
+                                    push(@unsolved,$partslist);
+                                }
+                            }
+                        }
                     }
                 }
             }
             $prevattempts.=&start_data_table_row().
-                           '<td>'.&mt('Transaction [_1]',$version).'</td>';
+                           '<td>'.&mt('Transaction [_1]',$version);
+            if (@unsolved) {
+                $prevattempts .= '<span class="LC_nobreak"><label>'.
+                                 '<input type="checkbox" name="HIDE'.$identifier.'" value="'.$version.':'.join('_',@unsolved).'" />'.
+                                 &mt('Hide').'</label></span>';
+            }
+            $prevattempts .= '</td>';
             if (@hidden) {
                 foreach my $key (sort(keys(%lasthash))) {
                     next if ($key =~ /\.foilorder$/);
@@ -3866,9 +4471,15 @@ sub get_previous_attempt {
                         }
                     } else {
                         if ($key =~ /\./) {
-                            my $value = &format_previous_attempt_value($key,
-                                              $returnhash{$version.':'.$key});
-                            $prevattempts.='<td>'.$value.'&nbsp;</td>';
+                            my $value = $returnhash{$version.':'.$key};
+                            if ($key =~ /\.rndseed$/) {
+                                my ($id) = ($key =~ /^(.+)\.[^.]+$/);
+                                if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) {
+                                    $value = $returnhash{$version.':'.$id.'.rawrndseed'};
+                                }
+                            }
+                            $prevattempts.='<td>'.&format_previous_attempt_value($key,$value).
+                                           '&nbsp;</td>';
                         } else {
                             $prevattempts.='<td>&nbsp;</td>';
                         }
@@ -3877,9 +4488,15 @@ sub get_previous_attempt {
             } else {
 	        foreach my $key (sort(keys(%lasthash))) {
                     next if ($key =~ /\.foilorder$/);
-		    my $value = &format_previous_attempt_value($key,
-			            $returnhash{$version.':'.$key});
-		    $prevattempts.='<td>'.$value.'&nbsp;</td>';
+                    my $value = $returnhash{$version.':'.$key};
+                    if ($key =~ /\.rndseed$/) {
+                        my ($id) = ($key =~ /^(.+)\.[^.]+$/);
+                        if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) {
+                            $value = $returnhash{$version.':'.$id.'.rawrndseed'};
+                        }
+                    }
+                    $prevattempts.='<td>'.&format_previous_attempt_value($key,$value).
+                                   '&nbsp;</td>';
 	        }
             }
 	    $prevattempts.=&end_data_table_row();
@@ -4636,9 +5253,9 @@ sub blocking_status {
 # build a link to a popup window containing the details
     my $querystring  = "?activity=$activity";
 # $uname and $udom decide whose portfolio the user is trying to look at
-    if ($activity eq 'port') {
-        $querystring .= "&amp;udom=$udom"      if $udom;
-        $querystring .= "&amp;uname=$uname"    if $uname;
+    if (($activity eq 'port') || ($activity eq 'passwd')) {
+        $querystring .= "&amp;udom=$udom"      if ($udom =~ /^$match_domain$/); 
+        $querystring .= "&amp;uname=$uname"    if ($uname =~ /^$match_username$/);
     } elsif ($activity eq 'docs') {
         $querystring .= '&amp;url='.&HTML::Entities::encode($url,'&"');
     }
@@ -4657,13 +5274,17 @@ END_MYBLOCK
   
     my $popupUrl = "/adm/blockingstatus/$querystring";
     my $text = &mt('Communication Blocked');
+    my $class = 'LC_comblock';
     if ($activity eq 'docs') {
         $text = &mt('Content Access Blocked');
+        $class = '';
     } elsif ($activity eq 'printout') {
         $text = &mt('Printing Blocked');
+    } elsif ($activity eq 'passwd') {
+        $text = &mt('Password Changing Blocked');
     }
     $output .= <<"END_BLOCK";
-<div class='LC_comblock'>
+<div class='$class'>
   <a onclick='openWindow("$popupUrl","Blocking Table",600,300,"no","no");return false;' href='/adm/blockingstatus/$querystring'
   title='$text'>
   <img class='LC_noBorder LC_middle' title='$text' src='/res/adm/pages/comblock.png' alt='$text'/></a>
@@ -4679,22 +5300,44 @@ END_BLOCK
 ###############################################
 
 sub check_ip_acc {
-    my ($acc)=@_;
+    my ($acc,$clientip)=@_;
     &Apache::lonxml::debug("acc is $acc");
     if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) {
         return 1;
     }
-    my $allowed=0;
-    my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'};
+    my $allowed;
+    my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip;
 
     my $name;
-    foreach my $pattern (split(',',$acc)) {
-        $pattern =~ s/^\s*//;
-        $pattern =~ s/\s*$//;
+    my %access = (
+                     allowfrom => 1,
+                     denyfrom  => 0,
+                 );
+    my @allows;
+    my @denies;
+    foreach my $item (split(',',$acc)) {
+        $item =~ s/^\s*//;
+        $item =~ s/\s*$//;
+        my $pattern;
+        if ($item =~ /^\!(.+)$/) {
+            push(@denies,$1);
+        } else {
+            push(@allows,$item);
+        }
+   }
+   my $numdenies = scalar(@denies);
+   my $numallows = scalar(@allows);
+   my $count = 0;
+   foreach my $pattern (@denies,@allows) {
+        $count ++; 
+        my $acctype = 'allowfrom';
+        if ($count <= $numdenies) {
+            $acctype = 'denyfrom';
+        }
         if ($pattern =~ /\*$/) {
             #35.8.*
             $pattern=~s/\*//;
-            if ($ip =~ /^\Q$pattern\E/) { $allowed=1; }
+            if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; }
         } elsif ($pattern =~ /(\d+\.\d+\.\d+)\.\[(\d+)-(\d+)\]$/) {
             #35.8.3.[34-56]
             my $low=$2;
@@ -4702,7 +5345,7 @@ sub check_ip_acc {
             $pattern=$1;
             if ($ip =~ /^\Q$pattern\E/) {
                 my $last=(split(/\./,$ip))[3];
-                if ($last <=$high && $last >=$low) { $allowed=1; }
+                if ($last <=$high && $last >=$low) { $allowed=$access{$acctype}; }
             }
         } elsif ($pattern =~ /^\*/) {
             #*.msu.edu
@@ -4712,10 +5355,10 @@ sub check_ip_acc {
                 my $netaddr=inet_aton($ip);
                 ($name)=gethostbyaddr($netaddr,AF_INET);
             }
-            if ($name =~ /\Q$pattern\E$/i) { $allowed=1; }
+            if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; }
         } elsif ($pattern =~ /\d+\.\d+\.\d+\.\d+/) {
             #127.0.0.1
-            if ($ip =~ /^\Q$pattern\E/) { $allowed=1; }
+            if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; }
         } else {
             #some.name.com
             if (!defined($name)) {
@@ -4723,9 +5366,16 @@ sub check_ip_acc {
                 my $netaddr=inet_aton($ip);
                 ($name)=gethostbyaddr($netaddr,AF_INET);
             }
-            if ($name =~ /\Q$pattern\E$/i) { $allowed=1; }
+            if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; }
+        }
+        if ($allowed =~ /^(0|1)$/) { last; }
+    }
+    if ($allowed eq '') {
+        if ($numdenies && !$numallows) {
+            $allowed = 1;
+        } else {
+            $allowed = 0;
         }
-        if ($allowed) { last; }
     }
     return $allowed;
 }
@@ -4781,23 +5431,29 @@ sub get_domainconf {
             if (keys(%{$domconfig{'login'}})) {
                 foreach my $key (keys(%{$domconfig{'login'}})) {
                     if (ref($domconfig{'login'}{$key}) eq 'HASH') {
-                        if ($key eq 'loginvia') {
-                            if (ref($domconfig{'login'}{'loginvia'}) eq 'HASH') {
-                                foreach my $hostname (keys(%{$domconfig{'login'}{'loginvia'}})) {
-                                    if (ref($domconfig{'login'}{'loginvia'}{$hostname}) eq 'HASH') {
-                                        if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) {
-                                            my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'};
-                                            $designhash{$udom.'.login.loginvia'} = $server;
-                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') {
-
-                                                $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'};
-                                            } else {
-                                                $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'};
+                        if (($key eq 'loginvia') || ($key eq 'headtag')) {
+                            if (ref($domconfig{'login'}{$key}) eq 'HASH') {
+                                foreach my $hostname (keys(%{$domconfig{'login'}{$key}})) {
+                                    if (ref($domconfig{'login'}{$key}{$hostname}) eq 'HASH') {
+                                        if ($key eq 'loginvia') {
+                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) {
+                                                my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'};
+                                                $designhash{$udom.'.login.loginvia'} = $server;
+                                                if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') {
+
+                                                    $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'};
+                                                } else {
+                                                    $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'};
+                                                }
                                             }
-                                            if ($domconfig{'login'}{'loginvia'}{$hostname}{'exempt'}) {
-                                                $designhash{$udom.'.login.loginvia_exempt_'.$hostname} = $domconfig{'login'}{'loginvia'}{$hostname}{'exempt'};
+                                        } elsif ($key eq 'headtag') {
+                                            if ($domconfig{'login'}{'headtag'}{$hostname}{'url'}) {
+                                                $designhash{$udom.'.login.headtag_'.$hostname} = $domconfig{'login'}{'headtag'}{$hostname}{'url'};
                                             }
                                         }
+                                        if ($domconfig{'login'}{$key}{$hostname}{'exempt'}) {
+                                            $designhash{$udom.'.login.'.$key.'_exempt_'.$hostname} = $domconfig{'login'}{$key}{$hostname}{'exempt'};
+                                        }
                                     }
                                 }
                             }
@@ -5054,10 +5710,20 @@ sub CSTR_pageheader {
         $lastitem = $thisdisfn;
     }
 
+    my ($crsauthor,$title);
+    if (($env{'request.course.id'}) &&
+        ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) &&
+        ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) {
+        $crsauthor = 1;
+        $title = &mt('Course Authoring Space');
+    } else {
+        $title = &mt('Authoring Space');
+    }
+
     my $output =
          '<div>'
         .&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it?
-        .'<b>'.&mt('Authoring Space:').'</b> '
+        .'<b>'.$title.'</b> '
         .'<form name="dirs" method="post" action="'.$formaction
         .'" target="_top">' #FIXME lonpubdir: target="_parent"
         .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,'_top','/priv/'.$udom,undef,undef);
@@ -5068,13 +5734,18 @@ sub CSTR_pageheader {
             .$lastitem
             .'</span>';
     }
-    $output .=
-         '<br />'
-        #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/','_top','/priv','','+1',1)."</b></tt><br />"
-        .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()')
-        .'</form>'
-        .&Apache::lonmenu::constspaceform()
-        .'</div>';
+
+    if ($crsauthor) {
+        $output .= '</form>'.&Apache::lonmenu::constspaceform();
+    } else {
+        $output .=
+             '<br />'
+            #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/','_top','/priv','','+1',1)."</b></tt><br />"
+            .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()')
+            .'</form>'
+            .&Apache::lonmenu::constspaceform();
+    }
+    $output .= '</div>';
 
     return $output;
 }
@@ -5118,9 +5789,6 @@ Inputs:
 
 =item * $args, optional argument valid values are
             no_auto_mt_title -> prevents &mt()ing the title arg
-            inherit_jsmath -> when creating popup window in a page,
-                              should it have jsmath forced on by the
-                              current page
 
 =item * $advtoolsref, optional argument, ref to an array containing
             inlineremote items to be added in "Functions" menu below
@@ -5188,7 +5856,7 @@ sub bodytag {
 
 # construct main body tag
     my $bodytag = "<body $extra_body_attr>".
-	&Apache::lontexconvert::init_math_support($args->{'inherit_jsmath'});
+	&Apache::lontexconvert::init_math_support();
 
     &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']);
 
@@ -5212,7 +5880,17 @@ sub bodytag {
         $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 && !$env{'browser.mobile'});
+    }
 
         if ($env{'request.state'} eq 'construct') { $forcereg=1; }
 
@@ -5223,7 +5901,7 @@ sub bodytag {
         $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) {
@@ -5341,7 +6019,6 @@ sub endbodytag {
     unless ((ref($args) eq 'HASH') && ($args->{'notbody'})) {
         $endbodytag='</body>';
     }
-    $endbodytag=&Apache::lontexconvert::jsMath_process()."\n".$endbodytag;
     if ( exists( $env{'internal.head.redirect'} ) ) {
         if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) {
 	    $endbodytag=
@@ -5516,6 +6193,17 @@ div.LC_confirm_box .LC_success img {
   vertical-align: middle;
 }
 
+.LC_maxwidth {
+  max-width: 100%;
+  height: auto;
+}
+
+.LC_textsize_mobile {
+  \@media only screen and (max-device-width: 480px) {
+      -webkit-text-size-adjust:100%; -moz-text-size-adjust:100%; -ms-text-size-adjust:100%;
+  }
+}
+
 .LC_icon {
   border: none;
   vertical-align: middle;
@@ -5621,6 +6309,12 @@ ul.LC_breadcrumb_tools_outerlist li {
     float: right;
 }
 
+.LC_placement_prog {
+    padding-right: 20px;
+    font-weight: bold;
+    font-size: 90%;
+}
+
 table#LC_title_bar td {
   background: $tabbg;
 }
@@ -5637,6 +6331,10 @@ table#LC_menubuttons img {
   vertical-align: middle;
 }
 
+.LC_breadcrumbs_hoverable {
+  background: $sidebg;
+}
+
 td.LC_table_cell_checkbox {
   text-align: center;
 }
@@ -6494,7 +7192,7 @@ div.LC_edit_problem_footer,
 div.LC_edit_problem_footer div,
 div.LC_edit_problem_editxml_header,
 div.LC_edit_problem_editxml_header div {
-  margin-top: 5px;
+  z-index: 100;
 }
 
 div.LC_edit_problem_header_title {
@@ -6510,14 +7208,17 @@ table.LC_edit_problem_header_title {
   background: $tabbg;
 }
 
-div.LC_edit_problem_discards {
-  float: left;
-  padding-bottom: 5px;
+div.LC_edit_actionbar {
+    background-color: $sidebg;
+    margin: 0;
+    padding: 0;
+    line-height: 200%;
 }
 
-div.LC_edit_problem_saves {
-  float: right;
-  padding-bottom: 5px;
+div.LC_edit_actionbar div{
+    padding: 0;
+    margin: 0;
+    display: inline-block;
 }
 
 .LC_edit_opt {
@@ -6533,6 +7234,10 @@ div.LC_edit_problem_saves {
     margin-left: 40px;
 }
 
+#LC_edit_problem_codemirror div{
+    margin-left: 0px;
+}
+
 img.stift {
   border-width: 0;
   vertical-align: middle;
@@ -6620,6 +7325,10 @@ fieldset {
   /* overflow: hidden; */
 }
 
+article.geogebraweb div {
+    margin: 0;
+}
+
 fieldset > legend {
   font-weight: bold;
   padding: 0 5px 0 5px;
@@ -6647,7 +7356,6 @@ fieldset > legend {
 ol.LC_primary_menu {
   margin: 0;
   padding: 0;
-  background-color: $pgbg_or_bgcolor;
 }
 
 ol#LC_PathBreadcrumbs {
@@ -6659,23 +7367,48 @@ ol.LC_primary_menu li {
   vertical-align: middle;
   text-align: left;
   list-style: none;
+  position: relative;
   float: left;
+  z-index: 100; /* will be displayed above codemirror and underneath the help-layer */
+  line-height: 1.5em;
 }
 
-ol.LC_primary_menu li a {
+ol.LC_primary_menu li a,
+ol.LC_primary_menu li p {
   display: block;
   margin: 0;
   padding: 0 5px 0 10px;
   text-decoration: none;
 }
 
-ol.LC_primary_menu li ul {
+ol.LC_primary_menu li p span.LC_primary_menu_innertitle {
+  display: inline-block;
+  width: 95%;
+  text-align: left;
+}
+
+ol.LC_primary_menu li p span.LC_primary_menu_innerarrow {
+  display: inline-block;	
+  width: 5%;
+  float: right;
+  text-align: right;
+  font-size: 70%;
+}
+
+ol.LC_primary_menu ul {
   display: none;
-  width: 10em;
+  width: 15em;
   background-color: $data_table_light;
+  position: absolute;
+  top: 100%;
+}
+
+ol.LC_primary_menu ul ul {
+  left: 100%;
+  top: 0;
 }
 
-ol.LC_primary_menu li:hover ul, ol.LC_primary_menu li.hover ul {
+ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul {
   display: block;
   position: absolute;
   margin: 0;
@@ -6684,15 +7417,21 @@ ol.LC_primary_menu li:hover ul, ol.LC_pr
 }
 
 ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li {
+/* First Submenu -> size should be smaller than the menu title of the whole menu */
   font-size: 90%;
   vertical-align: top;
   float: none;
   border-left: 1px solid black;
   border-right: 1px solid black;
+/* A dark bottom border to visualize different menu options; 
+overwritten in the create_submenu routine for the last border-bottom of the menu */
+  border-bottom: 1px solid $data_table_dark; 
 }
 
-ol.LC_primary_menu li:hover li a, ol.LC_primary_menu li.hover li a {
-  background-color:$data_table_light;
+ol.LC_primary_menu li li p:hover {
+  color:$button_hover;
+  text-decoration:none;
+  background-color:$data_table_dark;
 }
 
 ol.LC_primary_menu li li a:hover {
@@ -6700,6 +7439,11 @@ ol.LC_primary_menu li li a:hover {
    background-color:$data_table_dark;
 }
 
+/* Font-size equal to the size of the predecessors*/
+ol.LC_primary_menu li:hover li li {
+  font-size: 100%;
+}
+
 ol.LC_primary_menu li img {
   vertical-align: bottom;
   height: 1.1em;
@@ -7242,6 +7986,16 @@ ul.LC_funclist li {
 }
 
 /*
+  styles used for response display
+*/
+div.LC_radiofoil, div.LC_rankfoil {
+  margin: .5em 0em .5em 0em;
+}
+table.LC_itemgroup {
+  margin-top: 1em;
+}
+
+/*
   styles used by TTH when "Default set of options to pass to tth/m
   when converting TeX" in course settings has been set
 
@@ -7262,6 +8016,87 @@ span.roman {font-family: serif; font-sty
 span.overacc2 {position: relative;  left: .8em; top: -1.2ex;}
 span.overacc1 {position: relative;  left: .6em; top: -1.2ex;}
 
+/*
+  sections with roles, for content only
+*/
+section[class^="role-"] {
+  padding-left: 10px;
+  padding-right: 5px;
+  margin-top: 8px;
+  margin-bottom: 8px;
+  border: 1px solid #2A4;
+  border-radius: 5px;
+  box-shadow: 0px 1px 1px #BBB;
+}
+section[class^="role-"]>h1 {
+  position: relative;
+  margin: 0px;
+  padding-top: 10px;
+  padding-left: 40px;
+}
+section[class^="role-"]>h1:before {
+  position: absolute;
+  left: -5px;
+  top: 5px;
+}
+section.role-activity>h1:before {
+  content:url('/adm/daxe/images/section_icons/activity.png');
+}
+section.role-advice>h1:before {
+  content:url('/adm/daxe/images/section_icons/advice.png');
+}
+section.role-bibliography>h1:before {
+  content:url('/adm/daxe/images/section_icons/bibliography.png');
+}
+section.role-citation>h1:before {
+  content:url('/adm/daxe/images/section_icons/citation.png');
+}
+section.role-conclusion>h1:before {
+  content:url('/adm/daxe/images/section_icons/conclusion.png');
+}
+section.role-definition>h1:before {
+  content:url('/adm/daxe/images/section_icons/definition.png');
+}
+section.role-demonstration>h1:before {
+  content:url('/adm/daxe/images/section_icons/demonstration.png');
+}
+section.role-example>h1:before {
+  content:url('/adm/daxe/images/section_icons/example.png');
+}
+section.role-explanation>h1:before {
+  content:url('/adm/daxe/images/section_icons/explanation.png');
+}
+section.role-introduction>h1:before {
+  content:url('/adm/daxe/images/section_icons/introduction.png');
+}
+section.role-method>h1:before {
+  content:url('/adm/daxe/images/section_icons/method.png');
+}
+section.role-more_information>h1:before {
+  content:url('/adm/daxe/images/section_icons/more_information.png');
+}
+section.role-objectives>h1:before {
+  content:url('/adm/daxe/images/section_icons/objectives.png');
+}
+section.role-prerequisites>h1:before {
+  content:url('/adm/daxe/images/section_icons/prerequisites.png');
+}
+section.role-remark>h1:before {
+  content:url('/adm/daxe/images/section_icons/remark.png');
+}
+section.role-reminder>h1:before {
+  content:url('/adm/daxe/images/section_icons/reminder.png');
+}
+section.role-summary>h1:before {
+  content:url('/adm/daxe/images/section_icons/summary.png');
+}
+section.role-syntax>h1:before {
+  content:url('/adm/daxe/images/section_icons/syntax.png');
+}
+section.role-warning>h1:before {
+  content:url('/adm/daxe/images/section_icons/warning.png');
+}
+
 END
 }
 
@@ -7354,6 +8189,82 @@ sub headtag {
 <meta http-equiv="pragma" content="no-cache" />
 <meta http-equiv="Refresh" content="$time; url=$url" />
 ADDMETA
+    } else {
+        unless (($args->{'frameset'}) || ($args->{'js_ready'}) || ($args->{'only_body'}) || ($args->{'no_nav_bar'})) {
+            my $requrl = $env{'request.uri'};
+            if ($requrl eq '') {
+                $requrl = $ENV{'REQUEST_URI'};
+                $requrl =~ s/\?.+$//;
+            }
+            unless (($requrl =~ m{^/adm/(?:switchserver|login|authenticate|logout|groupsort|cleanup|helper|slotrequest|grades)(\?|$)}) ||
+                    (($requrl =~ m{^/res/}) && (($env{'form.submitted'} eq 'scantron') ||
+                     ($env{'form.grade_symb'}) || ($Apache::lonhomework::scantronmode)))) {
+                my $dom_in_use = $Apache::lonnet::perlvar{'lonDefDomain'};
+                unless (&Apache::lonnet::allowed('mau',$dom_in_use)) {
+                    my %domdefs = &Apache::lonnet::get_domain_defaults($dom_in_use);
+                    if (ref($domdefs{'offloadnow'}) eq 'HASH') {
+                        my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+                        if ($domdefs{'offloadnow'}{$lonhost}) {
+                            my $newserver = &Apache::lonnet::spareserver(30000,undef,1,$dom_in_use);
+                            if (($newserver) && ($newserver ne $lonhost)) {
+                                my $numsec = 5;
+                                my $timeout = $numsec * 1000;
+                                my ($newurl,$locknum,%locks,$msg);
+                                if ($env{'request.role.adv'}) {
+                                    ($locknum,%locks) = &Apache::lonnet::get_locks();
+                                }
+                                my $disable_submit = 0;
+                                if ($requrl =~ /$LONCAPA::assess_re/) {
+                                    $disable_submit = 1;
+                                }
+                                if ($locknum) {
+                                    my @lockinfo = sort(values(%locks));
+                                    $msg = &mt('Once the following tasks are complete: ')."\\n".
+                                           join(", ",sort(values(%locks)))."\\n".
+                                           &mt('your session will be transferred to a different server, after you click "Roles".');
+                                } else {
+                                    if (($requrl =~ m{^/res/}) && ($env{'form.submitted'} =~ /^part_/)) {
+                                        $msg = &mt('Your LON-CAPA submission has been recorded')."\\n";
+                                    }
+                                    $msg .= &mt('Your current LON-CAPA session will be transferred to a different server in [quant,_1,second].',$numsec);
+                                    $newurl = '/adm/switchserver?otherserver='.$newserver;
+                                    if (($env{'request.role'}) && ($env{'request.role'} ne 'cm')) {
+                                        $newurl .= '&role='.$env{'request.role'};
+                                    }
+                                    if ($env{'request.symb'}) {
+                                        $newurl .= '&symb='.$env{'request.symb'};
+                                    } else {
+                                        $newurl .= '&origurl='.$requrl;
+                                    }
+                                }
+                                &js_escape(\$msg);
+                                $result.=<<OFFLOAD
+<meta http-equiv="pragma" content="no-cache" />
+<script type="text/javascript">
+// <![CDATA[
+function LC_Offload_Now() {
+    var dest = "$newurl";
+    if (dest != '') {
+        window.location.href="$newurl";
+    }
+}
+\$(document).ready(function () {
+    window.alert('$msg');
+    if ($disable_submit) {
+        \$(".LC_hwk_submit").prop("disabled", true);
+        \$( ".LC_textline" ).prop( "readonly", "readonly");
+    }
+    setTimeout('LC_Offload_Now()', $timeout);
+});
+// ]]>
+</script>
+OFFLOAD
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
     if (!defined($title)) {
 	$title = 'The LearningOnline Network with CAPA';
@@ -7367,7 +8278,13 @@ ADDMETA
     $result .= '>' 
         .$inhibitprint
 	.$head_extra;
-    if ($env{'browser.mobile'}) {
+    my $clientmobile;
+    if (($env{'user.name'} eq '') && ($env{'user.domain'} eq '')) {
+        (undef,undef,undef,undef,undef,undef,$clientmobile) = &decode_user_agent();
+    } else {
+        $clientmobile = $env{'browser.mobile'};
+    }
+    if ($clientmobile) {
         $result .= '
 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
 <meta name="apple-mobile-web-app-capable" content="yes" />';
@@ -7549,9 +8466,6 @@ $args - additional optional args support
                                     head -> skip the <html><head> generation
                                     body -> skip all <body> generation
              no_auto_mt_title -> prevent &mt()ing the title arg
-             inherit_jsmath -> when creating popup window in a page,
-                                    should it have jsmath forced on by the
-                                    current page
              bread_crumbs ->             Array containing breadcrumbs
              bread_crumbs_component ->  if exists show it as headline else show only the breadcrumbs
              group          -> includes the current group, if page is for a 
@@ -7623,7 +8537,10 @@ sub start_page {
 		#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();
 		}
     }
@@ -7673,10 +8590,12 @@ function set_wishlistlink(title, path) {
         title = title.replace(/^LON-CAPA /,'');
     }
     title = encodeURIComponent(title);
+    title = title.replace("'","\\\'");
     if (!path) {
         path = location.pathname;
     }
     path = encodeURIComponent(path);
+    path = path.replace("'","\\\'");
     Win = window.open('/adm/wishlist?mode=newLink&setTitle='+title+'&setPath='+path,
                       'wishlistNewLink','width=560,height=350,scrollbars=0');
 }
@@ -7719,12 +8638,13 @@ var modalWindow = {
 };
 	var openMyModal = function(source,width,height,scrolling,transparency,style)
 	{
+                source = source.replace("'","&#39;");
 		modalWindow.windowId = "myModal";
 		modalWindow.width = width;
 		modalWindow.height = height;
-		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='"+transparency+"' src='" + source + "' style='"+style+"'>&lt/iframe>";
+		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='"+transparency+"' src='" + source + "' style='"+style+"'></iframe>";
 		modalWindow.open();
-	};	
+	};
 // END LON-CAPA Internal -->
 // ]]>
 </script>
@@ -8326,7 +9246,7 @@ role status: active, previous or future.
 sub check_user_status {
     my ($udom,$uname,$cdom,$crs,$role,$sec) = @_;
     my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname);
-    my @uroles = keys %userinfo;
+    my @uroles = keys(%userinfo);
     my $srchstr;
     my $active_chk = 'none';
     my $now = time;
@@ -8737,8 +9657,8 @@ Incoming parameters:
 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.
@@ -8812,7 +9732,8 @@ sub get_user_quota {
             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 '') {
@@ -8960,7 +9881,7 @@ Inputs: 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.
@@ -9058,7 +9979,7 @@ sub user_picker {
         }
         $srchterm = $srch->{'srchterm'};
     }
-    my %lt=&Apache::lonlocal::texthash(
+    my %html_lt=&Apache::lonlocal::texthash(
                     'usr'       => 'Search criteria',
                     'doma'      => 'Domain/institution to search',
                     'uname'     => 'username',
@@ -9071,6 +9992,8 @@ sub user_picker {
                     'exact'     => 'is',
                     'contains'  => 'contains',
                     'begins'    => 'begins with',
+                                       );
+    my %js_lt=&Apache::lonlocal::texthash(
                     'youm'      => "You must include some text to search for.",
                     'thte'      => "The text you are searching for must contain at least two characters when using a 'begins' type search.",
                     'thet'      => "The text you are searching for must contain at least three characters when using a 'contains' type search.",
@@ -9080,6 +10003,8 @@ sub user_picker {
                     'whse'      => "When searching by last,first you must include at least one character in the first name.",
                      'thfo'     => "The following need to be corrected before the search can be run:",
                                        );
+    &html_escape(\%html_lt);
+    &js_escape(\%js_lt);
     my $domform = &select_dom_form($currdom,'srchdomain',1,1);
     my $srchinsel = ' <select name="srchin">';
 
@@ -9094,10 +10019,10 @@ sub user_picker {
         next if ($option eq 'crs' && !$env{'request.course.id'});
         if ($curr_selected{'srchin'} eq $option) {
             $srchinsel .= ' 
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchinsel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
         }
     }
     $srchinsel .= "\n  </select>\n";
@@ -9106,10 +10031,10 @@ sub user_picker {
     foreach my $option ('lastname','lastfirst','uname') {
         if ($curr_selected{'srchby'} eq $option) {
             $srchbysel .= '
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchbysel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
          }
     }
     $srchbysel .= "\n  </select>\n";
@@ -9118,10 +10043,10 @@ sub user_picker {
     foreach my $option ('begins','contains','exact') {
         if ($curr_selected{'srchtype'} eq $option) {
             $srchtypesel .= '
-   <option value="'.$option.'" selected="selected">'.$lt{$option}.'</option>';
+   <option value="'.$option.'" selected="selected">'.$html_lt{$option}.'</option>';
         } else {
             $srchtypesel .= '
-   <option value="'.$option.'">'.$lt{$option}.'</option>';
+   <option value="'.$option.'">'.$html_lt{$option}.'</option>';
         }
     }
     $srchtypesel .= "\n  </select>\n";
@@ -9206,46 +10131,46 @@ function validateEntry(callingForm) {
 
     if (srchterm == "") {
         checkok = 0;
-        msg += "$lt{'youm'}\\n";
+        msg += "$js_lt{'youm'}\\n";
     }
 
     if (srchtype== 'begins') {
         if (srchterm.length < 2) {
             checkok = 0;
-            msg += "$lt{'thte'}\\n";
+            msg += "$js_lt{'thte'}\\n";
         }
     }
 
     if (srchtype== 'contains') {
         if (srchterm.length < 3) {
             checkok = 0;
-            msg += "$lt{'thet'}\\n";
+            msg += "$js_lt{'thet'}\\n";
         }
     }
     if (srchin == 'instd') {
         if (srchdomain == '') {
             checkok = 0;
-            msg += "$lt{'yomc'}\\n";
+            msg += "$js_lt{'yomc'}\\n";
         }
     }
     if (srchin == 'dom') {
         if (srchdomain == '') {
             checkok = 0;
-            msg += "$lt{'ymcd'}\\n";
+            msg += "$js_lt{'ymcd'}\\n";
         }
     }
     if (srchby == 'lastfirst') {
         if (srchterm.indexOf(",") == -1) {
             checkok = 0;
-            msg += "$lt{'whus'}\\n";
+            msg += "$js_lt{'whus'}\\n";
         }
         if (srchterm.indexOf(",") == srchterm.length -1) {
             checkok = 0;
-            msg += "$lt{'whse'}\\n";
+            msg += "$js_lt{'whse'}\\n";
         }
     }
     if (checkok == 0) {
-        alert("$lt{'thfo'}\\n"+msg);
+        alert("$js_lt{'thfo'}\\n"+msg);
         return;
     }
     if (checkok == 1) {
@@ -9263,10 +10188,10 @@ $new_user_create
 END_BLOCK
 
     $output .= &Apache::lonhtmlcommon::start_pick_box().
-               &Apache::lonhtmlcommon::row_title($lt{'doma'}).
+               &Apache::lonhtmlcommon::row_title($html_lt{'doma'}).
                $domform.
                &Apache::lonhtmlcommon::row_closure().
-               &Apache::lonhtmlcommon::row_title($lt{'usr'}).
+               &Apache::lonhtmlcommon::row_title($html_lt{'usr'}).
                $srchbysel.
                $srchtypesel. 
                '<input type="text" size="15" name="srchterm" value="'.$srchterm.'" />'.
@@ -9279,56 +10204,160 @@ END_BLOCK
 
 sub user_rule_check {
     my ($usershash,$checks,$alerts,$rulematch,$inst_results,$curr_rules,$got_rules) = @_;
-    my $response;
+    my ($response,%inst_response);
     if (ref($usershash) eq 'HASH') {
-        foreach my $user (keys(%{$usershash})) {
-            my ($uname,$udom) = split(/:/,$user);
-            next if ($udom eq '' || $uname eq '');
-            my ($id,$newuser);
-            if (ref($usershash->{$user}) eq 'HASH') {
-                $newuser = $usershash->{$user}->{'newuser'};
-                $id = $usershash->{$user}->{'id'};
-            }
-            my $inst_response;
+        if (keys(%{$usershash}) > 1) {
+            my (%by_username,%by_id,%userdoms);
+            my $checkid; 
             if (ref($checks) eq 'HASH') {
-                if (defined($checks->{'username'})) {
-                    ($inst_response,%{$inst_results->{$user}}) = 
-                        &Apache::lonnet::get_instuser($udom,$uname);
-                } elsif (defined($checks->{'id'})) {
-                    ($inst_response,%{$inst_results->{$user}}) =
-                        &Apache::lonnet::get_instuser($udom,undef,$id);
+                if ((!defined($checks->{'username'})) && (defined($checks->{'id'}))) {
+                    $checkid = 1;
+                }
+            }
+            foreach my $user (keys(%{$usershash})) {
+                my ($uname,$udom) = split(/:/,$user);
+                if ($checkid) {
+                    if (ref($usershash->{$user}) eq 'HASH') {
+                        if ($usershash->{$user}->{'id'} ne '') {
+                            $by_id{$udom}{$usershash->{$user}->{'id'}} = $uname; 
+                            $userdoms{$udom} = 1;
+                            if (ref($inst_results) eq 'HASH') {
+                                $inst_results->{$uname.':'.$udom} = {};
+                            }
+                        }
+                    }
+                } else {
+                    $by_username{$udom}{$uname} = 1;
+                    $userdoms{$udom} = 1;
+                    if (ref($inst_results) eq 'HASH') {
+                        $inst_results->{$uname.':'.$udom} = {};
+                    }
+                }
+            }
+            foreach my $udom (keys(%userdoms)) {
+                if (!$got_rules->{$udom}) {
+                    my %domconfig = &Apache::lonnet::get_dom('configuration',
+                                                             ['usercreation'],$udom);
+                    if (ref($domconfig{'usercreation'}) eq 'HASH') {
+                        foreach my $item ('username','id') {
+                            if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+                                $$curr_rules{$udom}{$item} =
+                                    $domconfig{'usercreation'}{$item.'_rule'};
+                            }
+                        }
+                    }
+                    $got_rules->{$udom} = 1;
+                }
+            }
+            if ($checkid) {
+                foreach my $udom (keys(%by_id)) {
+                    my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_id{$udom},'id');
+                    if ($outcome eq 'ok') {
+                        foreach my $id (keys(%{$by_id{$udom}})) {
+                            my $uname = $by_id{$udom}{$id};
+                            $inst_response{$uname.':'.$udom} = $outcome;
+                        }
+                        if (ref($results) eq 'HASH') {
+                            foreach my $uname (keys(%{$results})) {
+                                if (exists($inst_response{$uname.':'.$udom})) {
+                                    $inst_response{$uname.':'.$udom} = $outcome;
+                                    $inst_results->{$uname.':'.$udom} = $results->{$uname};
+                                }
+                            }
+                        }
+                    }
                 }
             } else {
-                ($inst_response,%{$inst_results->{$user}}) =
-                    &Apache::lonnet::get_instuser($udom,$uname);
-                return;
+                foreach my $udom (keys(%by_username)) {
+                    my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_username{$udom});
+                    if ($outcome eq 'ok') {
+                        foreach my $uname (keys(%{$by_username{$udom}})) {
+                            $inst_response{$uname.':'.$udom} = $outcome;
+                        }
+                        if (ref($results) eq 'HASH') {
+                            foreach my $uname (keys(%{$results})) {
+                                $inst_results->{$uname.':'.$udom} = $results->{$uname};
+                            }
+                        }
+                    }
+                }
             }
-            if (!$got_rules->{$udom}) {
-                my %domconfig = &Apache::lonnet::get_dom('configuration',
-                                                  ['usercreation'],$udom);
-                if (ref($domconfig{'usercreation'}) eq 'HASH') {
-                    foreach my $item ('username','id') {
-                        if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
-                            $$curr_rules{$udom}{$item} = 
-                                $domconfig{'usercreation'}{$item.'_rule'};
+        } elsif (keys(%{$usershash}) == 1) {
+            my $user = (keys(%{$usershash}))[0];
+            my ($uname,$udom) = split(/:/,$user);
+            if (($udom ne '') && ($uname ne '')) {
+                if (ref($usershash->{$user}) eq 'HASH') {
+                    if (ref($checks) eq 'HASH') {
+                        if (defined($checks->{'username'})) {
+                            ($inst_response{$user},%{$inst_results->{$user}}) = 
+                                &Apache::lonnet::get_instuser($udom,$uname);
+                        } elsif (defined($checks->{'id'})) {
+                            if ($usershash->{$user}->{'id'} ne '') {
+                                ($inst_response{$user},%{$inst_results->{$user}}) =
+                                    &Apache::lonnet::get_instuser($udom,undef,
+                                                                  $usershash->{$user}->{'id'});
+                            } else {
+                                ($inst_response{$user},%{$inst_results->{$user}}) =
+                                    &Apache::lonnet::get_instuser($udom,$uname);
+                            }
                         }
+                    } else {
+                       ($inst_response{$user},%{$inst_results->{$user}}) =
+                            &Apache::lonnet::get_instuser($udom,$uname);
+                       return;
+                    }
+                    if (!$got_rules->{$udom}) {
+                        my %domconfig = &Apache::lonnet::get_dom('configuration',
+                                                                 ['usercreation'],$udom);
+                        if (ref($domconfig{'usercreation'}) eq 'HASH') {
+                            foreach my $item ('username','id') {
+                                if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+                                   $$curr_rules{$udom}{$item} = 
+                                       $domconfig{'usercreation'}{$item.'_rule'};
+                                }
+                            }
+                        }
+                        $got_rules->{$udom} = 1;
                     }
                 }
-                $got_rules->{$udom} = 1;  
+            } else {
+                return;
+            }
+        } else {
+            return;
+        }
+        foreach my $user (keys(%{$usershash})) {
+            my ($uname,$udom) = split(/:/,$user);
+            next if (($udom eq '') || ($uname eq ''));
+            my $id;
+            if (ref($inst_results) eq 'HASH') {
+                if (ref($inst_results->{$user}) eq 'HASH') {
+                    $id = $inst_results->{$user}->{'id'};
+                }
+            }
+            if ($id eq '') { 
+                if (ref($usershash->{$user})) {
+                    $id = $usershash->{$user}->{'id'};
+                }
             }
             foreach my $item (keys(%{$checks})) {
                 if (ref($$curr_rules{$udom}) eq 'HASH') {
                     if (ref($$curr_rules{$udom}{$item}) eq 'ARRAY') {
                         if (@{$$curr_rules{$udom}{$item}} > 0) {
-                            my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,$$curr_rules{$udom}{$item});
+                            my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,
+                                                                             $$curr_rules{$udom}{$item});
                             foreach my $rule (@{$$curr_rules{$udom}{$item}}) {
                                 if ($rule_check{$rule}) {
                                     $$rulematch{$user}{$item} = $rule;
-                                    if ($inst_response eq 'ok') {
+                                    if ($inst_response{$user} eq 'ok') {
                                         if (ref($inst_results) eq 'HASH') {
                                             if (ref($inst_results->{$user}) eq 'HASH') {
                                                 if (keys(%{$inst_results->{$user}}) == 0) {
                                                     $$alerts{$item}{$udom}{$uname} = 1;
+                                                } elsif ($item eq 'id') {
+                                                    if ($inst_results->{$user}->{'id'} eq '') {
+                                                        $$alerts{$item}{$udom}{$uname} = 1;
+                                                    }
                                                 }
                                             }
                                         }
@@ -9596,7 +10625,9 @@ reservable_now - ref to hash of student_
 
     Keys in inner hash are:
     (a) symb: either blank or symb to which slot use is restricted.
-    (b) endreserve: end date of reservation period. 
+    (b) endreserve: end date of reservation period.
+    (c) uniqueperiod: start,end dates when slot is to be uniquely
+        selected.
 
 sorted_future - ref to array of student_schedulable slots reservable in
                 the future, ordered by start date of reservation period.
@@ -9606,7 +10637,9 @@ future_reservable - ref to hash of stude
 
     Keys in inner hash are:
     (a) symb: either blank or symb to which slot use is restricted.
-    (b) startreserve:  start date of reservation period.
+    (b) startreserve: start date of reservation period.
+    (c) uniqueperiod: start,end dates when slot is to be uniquely
+        selected.
 
 =back
 
@@ -9614,13 +10647,35 @@ future_reservable - ref to hash of stude
 
 sub get_future_slots {
     my ($cnum,$cdom,$now,$symb) = @_;
+    my $map;
+    if ($symb) {
+        ($map) = &Apache::lonnet::decode_symb($symb);
+    }
     my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future);
     my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom);
     foreach my $slot (keys(%slots)) {
         next unless($slots{$slot}->{'type'} eq 'schedulable_student');
         if ($symb) {
-            next if (($slots{$slot}->{'symb'} ne '') && 
-                     ($slots{$slot}->{'symb'} ne $symb));
+            if ($slots{$slot}->{'symb'} ne '') {
+                my $canuse;
+                my %oksymbs;
+                my @slotsymbs = split(/\s*,\s*/,$slots{$slot}->{'symb'});
+                map { $oksymbs{$_} = 1; } @slotsymbs;
+                if ($oksymbs{$symb}) {
+                    $canuse = 1;
+                } else {
+                    foreach my $item (@slotsymbs) {
+                        if ($item =~ /\.(page|sequence)$/) {
+                            (undef,undef,my $sloturl) = &Apache::lonnet::decode_symb($item);
+                            if (($map ne '') && ($map eq $sloturl)) {
+                                $canuse = 1;
+                                last;
+                            }
+                        }
+                    }
+                }
+                next unless ($canuse);
+            }
         }
         if (($slots{$slot}->{'starttime'} > $now) &&
             ($slots{$slot}->{'endtime'} > $now)) {
@@ -9660,6 +10715,10 @@ sub get_future_slots {
             my $startreserve = $slots{$slot}->{'startreserve'};
             my $endreserve = $slots{$slot}->{'endreserve'};
             my $symb = $slots{$slot}->{'symb'};
+            my $uniqueperiod;
+            if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') {
+                $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}});
+            }
             if (($startreserve < $now) &&
                 (!$endreserve || $endreserve > $now)) {
                 my $lastres = $endreserve;
@@ -9668,13 +10727,15 @@ sub get_future_slots {
                 }
                 $reservable_now{$slot} = {
                                            symb       => $symb,
-                                           endreserve => $lastres
+                                           endreserve => $lastres,
+                                           uniqueperiod => $uniqueperiod,
                                          };
             } elsif (($startreserve > $now) &&
                      (!$endreserve || $endreserve > $startreserve)) {
                 $future_reservable{$slot} = {
                                               symb         => $symb,
-                                              startreserve => $startreserve
+                                              startreserve => $startreserve,
+                                              uniqueperiod => $uniqueperiod,
                                             };
             }
         }
@@ -9832,7 +10893,23 @@ sub get_env_multiple {
     return(@values);
 }
 
+# Looks at given dependencies, and returns something depending on the context.
+# For coursedocs paste, returns (undef, $counter, $numpathchg, \%existing).
+# For syllabus rewrites, returns (undef, $counter, $numpathchg, \%existing, \%mapping).
+# For all other contexts, returns ($output, $counter, $numpathchg).
+# $output: string with the HTML output. Can contain missing dependencies with an upload form, existing dependencies, and dependencies no longer in use.
+# $counter: integer with the number of existing dependencies when no HTML output is returned, and the number of missing dependencies when an HTML output is returned.
+# $numpathchg: integer with the number of cleaned up dependency paths.
+# \%existing: hash reference clean path -> 1 only for existing dependencies.
+# \%mapping: hash reference clean path -> original path for all dependencies.
+# @param {string} actionurl - The path to the handler, indicative of the context.
+# @param {string} state - Can contain HTML with hidden inputs that will be added to the output form.
+# @param {hash reference} allfiles - List of file info from lonnet::extract_embedded_items
+# @param {hash reference} codebase - undef, not modified by lonnet::extract_embedded_items ?
+# @param {hash reference} args - More parameters ! Possible keys: error_on_invalid_names (boolean), ignore_remote_references (boolean), current_path (string), docs_url (string), docs_title (string), context (string)
+# @return {Array} - array depending on the context (not a reference)
 sub ask_for_embedded_content {
+    # NOTE: documentation was added afterwards, it could be wrong
     my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
     my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges,
         %currsubfile,%unused,$rem);
@@ -9848,6 +10925,9 @@ sub ask_for_embedded_content {
     my $heading = &mt('Upload embedded files');
     my $buttontext = &mt('Upload');
 
+    # fills these variables based on the context:
+    # $navmap, $cdom, $cnum, $udom, $uname, $url, $toplevel, $getpropath,
+    # $path, $fileloc, $title, $rem, $filename
     if ($env{'request.course.id'}) {
         if ($actionurl eq '/adm/dependencies') {
             $navmap = Apache::lonnavmaps::navmap->new();
@@ -9909,7 +10989,15 @@ sub ask_for_embedded_content {
                     ($path) =
                         ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
                 }
-                $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+                if ($toplevel=~/^\/*(uploaded|editupload)/) {
+                    $fileloc = $toplevel;
+                    $fileloc=~ s/^\s*(\S+)\s*$/$1/;
+                    my ($udom,$uname,$fname) =
+                        ($fileloc=~ m{^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$});
+                    $fileloc = propath($udom,$uname).'/userfiles/'.$fname;
+                } else {
+                    $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+                }
                 $fileloc =~ s{^/}{};
                 ($filename) = ($fileloc =~ m{.+/([^/]+)$});
                 $heading = &mt('Status of dependencies in [_1]',"$title ($filename)");
@@ -9924,6 +11012,16 @@ sub ask_for_embedded_content {
         $fileloc = &Apache::lonnet::filelocation('',$toplevel).'/';
         $fileloc =~ s{^/}{};
     }
+    
+    # parses the dependency paths to get some info
+    # fills $newfiles, $mapping, $subdependencies, $dependencies
+    # $newfiles: hash URL -> 1 for new files or external URLs
+    # (will be completed later)
+    # $mapping:
+    #   for external URLs: external URL -> external URL
+    #   for relative paths: clean path -> original path
+    # $subdependencies: hash clean path -> clean file name -> 1 for relative paths in subdirectories
+    # $dependencies: hash clean or not file name -> 1 for relative paths not in subdirectories
     foreach my $file (keys(%{$allfiles})) {
         my $embed_file;
         if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) {
@@ -9966,6 +11064,19 @@ sub ask_for_embedded_content {
             }
         }
     }
+    
+    # looks for all existing files in dependency subdirectories (from $subdependencies filled above)
+    # and lists
+    # fills $currsubfile, $pathchanges, $existing, $numexisting, $newfiles, $unused
+    # $currsubfile: hash clean path -> file name -> 1 for all existing files in the path
+    # $pathchanges: hash clean path -> 1 if the file in subdirectory exists and
+    #                                    the path had to be cleaned up
+    # $existing: hash clean path -> 1 if the file exists
+    # $numexisting: number of keys in $existing
+    # $newfiles: updated with clean path -> 1 for files in subdirectories that do not exist
+    # $unused: only for /adm/dependencies, hash clean path -> 1 for existing files in
+    #                                      dependency subdirectories that are
+    #                                      not listed as dependencies, with some exceptions using $rem
     my $dirptr = 16384;
     foreach my $path (keys(%subdependencies)) {
         $currsubfile{$path} = {};
@@ -10041,6 +11152,9 @@ sub ask_for_embedded_content {
             }
         }
     }
+    
+    # fills $currfile, hash file name -> 1 or [$size,$mtime]
+    # for files in $url or $fileloc (target directory) in some contexts
     my %currfile;
     if (($actionurl eq '/adm/portfolio') ||
         ($actionurl eq '/adm/coursegrp_portfolio')) {
@@ -10079,6 +11193,8 @@ sub ask_for_embedded_content {
             }
         }
     }
+    # updates $pathchanges, $existing, $numexisting, $newfiles and $unused for files that
+    # are not in subdirectories, using $currfile
     foreach my $file (keys(%dependencies)) {
         if (exists($currfile{$file})) {
             unless ($mapping{$file} eq $file) {
@@ -10107,6 +11223,8 @@ sub ask_for_embedded_content {
             $unused{$file} = 1;
         }
     }
+    
+    # returns some results for coursedocs paste and syllabus rewrites ($output is undef)
     if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
         ($args->{'context'} eq 'paste')) {
         $counter = scalar(keys(%existing));
@@ -10118,6 +11236,12 @@ sub ask_for_embedded_content {
         $numpathchg = scalar(keys(%pathchanges));
         return ($output,$counter,$numpathchg,\%existing,\%mapping);
     }
+    
+    # returns HTML otherwise, with dependency results and to ask for more uploads
+    
+    # $upload_output: missing dependencies (with upload form)
+    # $modify_output: uploaded dependencies (in use)
+    # $delete_output: files no longer in use (unused files are not listed for londocs, bug?)
     foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) {
         if ($actionurl eq '/adm/dependencies') {
             next if ($embed_file =~ m{^\w+://});
@@ -11065,7 +12189,7 @@ sub decompress_form {
                         "$topdir/media/player.swf",
                         "$topdir/media/swfobject.js",
                         "$topdir/media/expressInstall.swf");
-        my @camtasia8 = ("$topdir/","$topdir/$topdir.html",
+        my @camtasia8_1 = ("$topdir/","$topdir/$topdir.html",
                          "$topdir/$topdir.mp4",
                          "$topdir/$topdir\_config.xml",
                          "$topdir/$topdir\_controller.swf",
@@ -11087,13 +12211,36 @@ sub decompress_form {
                          "$topdir/skins/express_show/",
                          "$topdir/skins/express_show/player-min.css",
                          "$topdir/skins/express_show/spritesheet.png");
+        my @camtasia8_4 = ("$topdir/","$topdir/$topdir.html",
+                         "$topdir/$topdir.mp4",
+                         "$topdir/$topdir\_config.xml",
+                         "$topdir/$topdir\_controller.swf",
+                         "$topdir/$topdir\_embed.css",
+                         "$topdir/$topdir\_First_Frame.png",
+                         "$topdir/$topdir\_player.html",
+                         "$topdir/$topdir\_Thumbnails.png",
+                         "$topdir/playerProductInstall.swf",
+                         "$topdir/scripts/",
+                         "$topdir/scripts/config_xml.js",
+                         "$topdir/scripts/techsmith-smart-player.min.js",
+                         "$topdir/skins/",
+                         "$topdir/skins/configuration_express.xml",
+                         "$topdir/skins/express_show/",
+                         "$topdir/skins/express_show/spritesheet.min.css",
+                         "$topdir/skins/express_show/spritesheet.png",
+                         "$topdir/skins/express_show/techsmith-smart-player.min.css");
         my @diffs = &compare_arrays(\@paths,\@camtasia6);
         if (@diffs == 0) {
             $is_camtasia = 6;
         } else {
-            @diffs = &compare_arrays(\@paths,\@camtasia8);
+            @diffs = &compare_arrays(\@paths,\@camtasia8_1);
             if (@diffs == 0) {
                 $is_camtasia = 8;
+            } else {
+                @diffs = &compare_arrays(\@paths,\@camtasia8_4);
+                if (@diffs == 0) {
+                    $is_camtasia = 8;
+                }
             }
         }
     }
@@ -11107,7 +12254,6 @@ function camtasiaToggle() {
     for (var i=0; i<document.uploaded_decompress.autoextract_camtasia.length; i++) {
         if (document.uploaded_decompress.autoextract_camtasia[i].checked) {
             if (document.uploaded_decompress.autoextract_camtasia[i].value == $is_camtasia) {
-
                 document.getElementById('camtasia_titles').style.display='block';
             } else {
                 document.getElementById('camtasia_titles').style.display='none';
@@ -13338,6 +14484,87 @@ sub build_recipient_list {
 
 =pod
 
+=over 4
+
+=item * &mime_email()
+
+Sends an email with a possible attachment
+
+Inputs:
+
+=over 4
+
+from -              Sender's email address
+
+to -                Email address of recipient
+
+subject -           Subject of email
+
+body -              Body of email
+
+cc_string -         Carbon copy email address
+
+bcc -               Blind carbon copy email address
+
+type -              File type of attachment
+
+attachment_path -   Path of file to be attached
+
+file_name -         Name of file to be attached
+
+attachment_text -   The body of an attachment of type "TEXT"
+
+=back
+
+=back
+
+=cut
+
+############################################################
+############################################################
+
+sub mime_email {
+    my ($from, $to, $subject, $body, $cc_string, $bcc, $attachment_path, 
+        $file_name, $attachment_text) = @_;
+    my $msg = MIME::Lite->new(
+             From    => $from,
+             To      => $to,
+             Subject => $subject,
+             Type    =>'TEXT',
+             Data    => $body,
+             );
+    if ($cc_string ne '') {
+        $msg->add("Cc" => $cc_string);
+    }
+    if ($bcc ne '') {
+        $msg->add("Bcc" => $bcc);
+    }
+    $msg->attr("content-type"         => "text/plain");
+    $msg->attr("content-type.charset" => "UTF-8");
+    # Attach file if given
+    if ($attachment_path) {
+        unless ($file_name) {
+            if ($attachment_path =~ m-/([^/]+)$-) { $file_name = $1; }
+        }
+        my ($type, $encoding) = MIME::Types::by_suffix($attachment_path);
+        $msg->attach(Type     => $type,
+                     Path     => $attachment_path,
+                     Filename => $file_name
+                     );
+    # Otherwise attach text if given
+    } elsif ($attachment_text) {
+        $msg->attach(Type => 'TEXT',
+                     Data => $attachment_text);
+    }
+    # Send it
+    $msg->send('sendmail');
+}
+
+############################################################
+############################################################
+
+=pod
+
 =head1 Course Catalog Routines
 
 =over 4
@@ -13440,6 +14667,8 @@ sub extract_categories {
                     $trailstr = &mt('Official courses (with institutional codes)');
                 } elsif ($name eq 'communities') {
                     $trailstr = &mt('Communities');
+                } elsif ($name eq 'placement') {
+                    $trailstr = &mt('Placement Tests');
                 } else {
                     $trailstr = $name;
                 }
@@ -13578,8 +14807,10 @@ sub assign_categories_table {
                     next if ($parent eq 'instcode');
                     if ($type eq 'Community') {
                         next unless ($parent eq 'communities');
+                    } elsif ($type eq 'Placement') {
+                        next unless ($parent eq 'placement');
                     } else {
-                        next if ($parent eq 'communities');
+                        next if (($parent eq 'communities') || ($parent eq 'placement'));
                     }
                     my $css_class = $itemcount%2?' class="LC_odd_row"':'';
                     my $item = &escape($parent).'::0';
@@ -13592,6 +14823,8 @@ sub assign_categories_table {
                     my $parent_title = $parent;
                     if ($parent eq 'communities') {
                         $parent_title = &mt('Communities');
+                    } elsif ($parent eq 'placement') {
+                        $parent_title = &mt('Placement Tests');
                     }
                     $table .= '<tr '.$css_class.'><td><span class="LC_nobreak">'.
                               '<input type="checkbox" name="usecategory" value="'.
@@ -13906,34 +15139,92 @@ sub check_clone {
             (&Apache::lonnet::allowed('ccc',$env{'request.role.domain'}))) {
 	    $can_clone = 1;
 	} else {
-	    my %clonehash = &Apache::lonnet::get('environment',['cloners'],
+	    my %clonehash = &Apache::lonnet::get('environment',['cloners','internal.coursecode'],
 						 $args->{'clonedomain'},$args->{'clonecourse'});
-	    my @cloners = split(/,/,$clonehash{'cloners'});
-            if (grep(/^\*$/,@cloners)) {
-                $can_clone = 1;
-            } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
-                $can_clone = 1;
+            if ($clonehash{'cloners'} eq '') {
+                my %domdefs = &Apache::lonnet::get_domain_defaults($args->{'course_domain'});
+                if ($domdefs{'canclone'}) {
+                    unless ($domdefs{'canclone'} eq 'none') {
+                        if ($domdefs{'canclone'} eq 'domain') {
+                            if ($args->{'ccdomain'} eq $args->{'clonedomain'}) {
+                                $can_clone = 1;
+                            }
+                        } elsif (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) && 
+                                 ($args->{'clonedomain'} eq  $args->{'course_domain'})) {
+                            if (&Apache::lonnet::default_instcode_cloning($args->{'clonedomain'},$domdefs{'canclone'},
+                                                                          $clonehash{'internal.coursecode'},$args->{'crscode'})) {
+                                $can_clone = 1;
+                            }
+                        }
+                    }
+                }
             } else {
+	        my @cloners = split(/,/,$clonehash{'cloners'});
+                if (grep(/^\*$/,@cloners)) {
+                    $can_clone = 1;
+                } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+                    $can_clone = 1;
+                } elsif (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+                    $can_clone = 1;
+                }
+                unless ($can_clone) {
+                    if (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) && 
+                        ($args->{'clonedomain'} eq  $args->{'course_domain'})) {
+                        my (%gotdomdefaults,%gotcodedefaults);
+                        foreach my $cloner (@cloners) {
+                            if (($cloner ne '*') && ($cloner !~ /^\*\:$match_domain$/) &&
+                                ($cloner !~ /^$match_username\:$match_domain$/) && ($cloner ne '')) {
+                                my (%codedefaults,@code_order);
+                                if (ref($gotcodedefaults{$args->{'clonedomain'}}) eq 'HASH') {
+                                    if (ref($gotcodedefaults{$args->{'clonedomain'}}{'defaults'}) eq 'HASH') {
+                                        %codedefaults = %{$gotcodedefaults{$args->{'clonedomain'}}{'defaults'}};
+                                    }
+                                    if (ref($gotcodedefaults{$args->{'clonedomain'}}{'order'}) eq 'ARRAY') {
+                                        @code_order = @{$gotcodedefaults{$args->{'clonedomain'}}{'order'}};
+                                    }
+                                } else {
+                                    &Apache::lonnet::auto_instcode_defaults($args->{'clonedomain'},
+                                                                            \%codedefaults,
+                                                                            \@code_order);
+                                    $gotcodedefaults{$args->{'clonedomain'}}{'defaults'} = \%codedefaults;
+                                    $gotcodedefaults{$args->{'clonedomain'}}{'order'} = \@code_order;
+                                }
+                                if (@code_order > 0) {
+                                    if (&Apache::lonnet::check_instcode_cloning(\%codedefaults,\@code_order,
+                                                                                $cloner,$clonehash{'internal.coursecode'},
+                                                                                $args->{'crscode'})) {
+                                        $can_clone = 1;
+                                        last;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            unless ($can_clone) {
                 my $ccrole = 'cc';
                 if ($args->{'crstype'} eq 'Community') {
                     $ccrole = 'co';
                 }
 	        my %roleshash =
 		    &Apache::lonnet::get_my_roles($args->{'ccuname'},
-					 $args->{'ccdomain'},
-                                         'userroles',['active'],[$ccrole],
-					 [$args->{'clonedomain'}]);
-	        if (($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) || (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners))) {
+					          $args->{'ccdomain'},
+                                                  'userroles',['active'],[$ccrole],
+					          [$args->{'clonedomain'}]);
+	        if ($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) {
                     $can_clone = 1;
-                } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},$args->{'ccuname'},$args->{'ccdomain'})) {
+                } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},
+                                                          $args->{'ccuname'},$args->{'ccdomain'})) {
                     $can_clone = 1;
+                }
+            }
+            unless ($can_clone) {
+                if ($args->{'crstype'} eq 'Community') {
+                    $clonemsg = &mt('No new community created.').$linefeed.&mt('The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
                 } else {
-                    if ($args->{'crstype'} eq 'Community') {
-                        $clonemsg = &mt('No new community created.').$linefeed.&mt('The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
-                    } else {
-                        $clonemsg = &mt('No new course created.').$linefeed.&mt('The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
-                    }
-	        }
+                    $clonemsg = &mt('No new course created.').$linefeed.&mt('The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
+                }
 	    }
         }
     }
@@ -13969,7 +15260,12 @@ sub construct_course {
 #
 # Open course
 #
-    my $crstype = lc($args->{'crstype'});
+    my $showncrstype;
+    if ($args->{'crstype'} eq 'Placement') {
+        $showncrstype = 'placement test'; 
+    } else {  
+        $showncrstype = lc($args->{'crstype'});
+    }
     my %cenv=();
     $$courseid=&Apache::lonnet::createcourse($args->{'course_domain'},
                                              $args->{'cdescr'},
@@ -13986,7 +15282,7 @@ sub construct_course {
     # Utils::Course. This needs to at least be output as a comment
     # if anyone ever decides to not show this, and Utils::Course::new
     # will need to be suitably modified.
-    $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$crstype,$$courseid).$linefeed;
+    $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$showncrstype,$$courseid).$linefeed;
     if ($$courseid =~ /^error:/) {
         return (0,$outcome);
     }
@@ -14006,7 +15302,7 @@ sub construct_course {
 # Do the cloning
 #   
     if ($can_clone && $cloneid) {
-	$clonemsg = &mt('Cloning [_1] from [_2]',$crstype,$clonehome);
+	$clonemsg = &mt('Cloning [_1] from [_2]',$showncrstype,$clonehome);
 	if ($context ne 'auto') {
 	    $clonemsg = '<span class="LC_success">'.$clonemsg.'</span>';
 	}
@@ -14311,6 +15607,30 @@ sub construct_course {
         $outcome .= ($fatal?$errtext:'write ok').$linefeed;
     }
 
+# 
+# Set params for Placement Tests
+#
+    if ($args->{'crstype'} eq 'Placement') {
+       my %storecontent; 
+       my $prefix=$$crsudom.'_'.$$crsunum.'.0.';
+       my %defaults = (
+                        buttonshide   => { value => 'yes',
+                                           type => 'string_yesno',},
+                        type          => { value => 'randomizetry',
+                                           type  => 'string_questiontype',},
+                        maxtries      => { value => 1,
+                                           type => 'int_pos',},
+                        problemstatus => { value => 'no',
+                                           type  => 'string_problemstatus',},
+                      );
+       foreach my $key (keys(%defaults)) {
+           $storecontent{$prefix.$key} = $defaults{$key}{'value'};
+           $storecontent{$prefix.$key.'.type'} = $defaults{$key}{'type'};
+       }
+       &Apache::lonnet::cput
+                 ('resourcedata',\%storecontent,$$crsudom,$$crsunum); 
+    }
+
     return (1,$outcome);
 }
 
@@ -14371,8 +15691,7 @@ sub generate_code {
 ############################################################
 ############################################################
 
-#SD
-# only Community and Course, or anything else?
+# Community, Course and Placement Test
 sub course_type {
     my ($cid) = @_;
     if (!defined($cid)) {
@@ -14390,17 +15709,19 @@ sub group_term {
     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);
 }
@@ -14461,7 +15782,7 @@ sub escape_url {
     my ($url)   = @_;
     my @urlslices = split(/\//, $url,-1);
     my $lastitem = &escape(pop(@urlslices));
-    return join('/',@urlslices).'/'.$lastitem;
+    return &HTML::Entities::encode(join('/',@urlslices),"'").'/'.$lastitem;
 }
 
 sub compare_arrays {
@@ -14519,6 +15840,17 @@ sub init_user_environment {
 		}
 	    }
 	    closedir(DIR);
+# If there is a undeleted lockfile for the user's paste buffer remove it.
+            my $namespace = 'nohist_courseeditor';
+            my $lockingkey = 'paste'."\0".'locked_num';
+            my %lockhash = &Apache::lonnet::get($namespace,[$lockingkey],
+                                                $domain,$username);
+            if (exists($lockhash{$lockingkey})) {
+                my $delresult = &Apache::lonnet::del($namespace,[$lockingkey],$domain,$username);
+                unless ($delresult eq 'ok') {
+                    &Apache::lonnet::logthis("Failed to delete paste buffer locking key in $namespace for ".$username.":".$domain." Result was: $delresult");
+                }
+            }
 	}
 # Give them a new cookie
 	my $id = ($args->{'robot'} ? 'robot'.$args->{'robot'}
@@ -14604,7 +15936,7 @@ sub init_user_environment {
                                                   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',
@@ -14852,15 +16184,19 @@ sub build_filters {
         $createdfilterform = &timebased_select_form('createdfilter',$filter);
     }
 
+    my $prefix = $crstype;
+    if ($crstype eq 'Placement') {
+        $prefix = 'Placement Test'
+    }
     my %lt = &Apache::lonlocal::texthash(
-                'cac' => "$crstype Activity",
-                'ccr' => "$crstype Created",
-                'cde' => "$crstype Title",
-                'cdo' => "$crstype Domain",
+                'cac' => "$prefix Activity",
+                'ccr' => "$prefix Created",
+                'cde' => "$prefix Title",
+                'cdo' => "$prefix Domain",
                 'ins' => 'Institutional Code',
                 'inc' => 'Institutional Categorization',
-                'cow' => "$crstype Owner/Co-owner",
-                'cop' => "$crstype Personnel Includes",
+                'cow' => "$prefix Owner/Co-owner",
+                'cop' => "$prefix Personnel Includes",
                 'cog' => 'Type',
              );
 
@@ -14868,6 +16204,8 @@ sub build_filters {
         my $typeval = 'Course';
         if ($crstype eq 'Community') {
             $typeval = 'Community';
+        } elsif ($crstype eq 'Placement') {
+            $typeval = 'Placement';
         }
         $typeselectform = '<input type="hidden" name="type" value="'.$typeval.'" />';
     } else {
@@ -14876,9 +16214,15 @@ sub build_filters {
             $typeselectform .= ' onchange="'.$onchange.'"';
         }
         $typeselectform .= '>'."\n";
-        foreach my $posstype ('Course','Community') {
+        foreach my $posstype ('Course','Community','Placement') {
+            my $shown;
+            if ($posstype eq 'Placement') {
+                $shown = &mt('Placement Test');
+            } else {
+                $shown = &mt($posstype);
+            }
             $typeselectform.='<option value="'.$posstype.'"'.
-                ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".&mt($posstype)."</option>\n";
+                ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".$shown."</option>\n";
         }
         $typeselectform.="</select>";
     }
@@ -14937,7 +16281,12 @@ sub build_filters {
         $output .= '<input type="hidden" name="phase" value="courselist" />'."\n".
                    '<input type="hidden" name="prevphase" value="'.
                    $prevphase.'" />'."\n";
-    } elsif ($formname ne 'quotacheck') {
+    } elsif ($formname eq 'quotacheck') {
+        $output .= qq|
+<input type="hidden" name="sortby" value="" />
+<input type="hidden" name="sortorder" value="" />
+|;
+    } else {
         my $name_input;
         if ($cnameelement ne '') {
             $name_input = '<input type="hidden" name="cnameelement" value="'.
@@ -15130,11 +16479,18 @@ cloneruname - optional username of new c
 
 clonerudom - optional domain of new course owner
 
-domcloner - Optional "domcloner" flag; has value=1 if user has ccc priv in domain being filtered by, 
+domcloner - optional "domcloner" flag; has value=1 if user has ccc priv in domain being filtered by, 
             (used when DC is using course creation form)
 
 codetitles - reference to array of titles of components in institutional codes (official courses).
 
+cc_clone - escaped comma separated list of courses for which course cloner has active CC role
+           (and so can clone automatically)
+
+reqcrsdom - domain of new course, where search_courses is used to identify potential courses to clone
+
+reqinstcode - institutional code of new course, where search_courses is used to identify potential 
+              courses to clone 
 
 Returns: %courses - hash of courses satisfying search criteria, keys = course IDs, values are corresponding colon-separated escaped description, institutional code, owner and type.
 
@@ -15145,7 +16501,8 @@ Side Effects: None
 
 
 sub search_courses {
-    my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles) = @_;
+    my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles,
+        $cc_clone,$reqcrsdom,$reqinstcode) = @_;
     my (%courses,%showcourses,$cloner);
     if (($filter->{'ownerfilter'} ne '') ||
         ($filter->{'ownerdomfilter'} ne '')) {
@@ -15193,10 +16550,10 @@ sub search_courses {
                                              $filter->{'combownerfilter'},
                                              $filter->{'coursefilter'},
                                              undef,undef,$type,$regexpok,undef,undef,
-                                             undef,undef,$cloner,$env{'form.cc_clone'},
+                                             undef,undef,$cloner,$cc_clone,
                                              $filter->{'cloneableonly'},
                                              $createdbefore,$createdafter,undef,
-                                             $domcloner);
+                                             $domcloner,undef,$reqcrsdom,$reqinstcode);
     if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) {
         my $ccrole;
         if ($type eq 'Community') {
@@ -15230,13 +16587,210 @@ sub search_courses {
     return %courses;
 }
 
+=pod
+
+=back
+
+=head1 Routines for version requirements for current course.
+
+=over 4
+
+=item * &check_release_required()
+
+Compares required LON-CAPA version with version on server, and
+if required version is newer looks for a server with the required version.
+
+Looks first at servers in user's owen domain; if none suitable, looks at
+servers in course's domain are permitted to host sessions for user's domain.
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$courseid - Course ID of current course
+
+$rolecode - User's current role in course (for switchserver query string).
+
+$required - LON-CAPA version needed by course (format: Major.Minor).
+
+
+Returns:
+
+$switchserver - query string tp append to /adm/switchserver call (if 
+                current server's LON-CAPA version is too old. 
+
+$warning - Message is displayed if no suitable server could be found.
+
+=cut
+
+sub check_release_required {
+    my ($loncaparev,$courseid,$rolecode,$required) = @_;
+    my ($switchserver,$warning);
+    if ($required ne '') {
+        my ($reqdmajor,$reqdminor) = ($required =~ /^(\d+)\.(\d+)$/);
+        my ($major,$minor) = ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+        if ($reqdmajor ne '' && $reqdminor ne '') {
+            my $otherserver;
+            if (($major eq '' && $minor eq '') ||
+                (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)))) {
+                my ($userdomserver) = &Apache::lonnet::choose_server($env{'user.domain'},undef,$required,1);
+                my $switchlcrev =
+                    &Apache::lonnet::get_server_loncaparev($env{'user.domain'},
+                                                           $userdomserver);
+                my ($swmajor,$swminor) = ($switchlcrev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+                if (($swmajor eq '' && $swminor eq '') || ($reqdmajor > $swmajor) ||
+                    (($reqdmajor == $swmajor) && ($reqdminor > $swminor))) {
+                    my $cdom = $env{'course.'.$courseid.'.domain'};
+                    if ($cdom ne $env{'user.domain'}) {
+                        my ($coursedomserver,$coursehostname) = &Apache::lonnet::choose_server($cdom,undef,$required,1);
+                        my $serverhomeID = &Apache::lonnet::get_server_homeID($coursehostname);
+                        my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+                        my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom);
+                        my %udomdefaults = &Apache::lonnet::get_domain_defaults($env{'user.domain'});
+                        my $remoterev = &Apache::lonnet::get_server_loncaparev($serverhomedom,$coursedomserver);
+                        my $canhost =
+                            &Apache::lonnet::can_host_session($env{'user.domain'},
+                                                              $coursedomserver,
+                                                              $remoterev,
+                                                              $udomdefaults{'remotesessions'},
+                                                              $defdomdefaults{'hostedsessions'});
+
+                        if ($canhost) {
+                            $otherserver = $coursedomserver;
+                        } else {
+                            $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'<br />'. &mt("No suitable server could be found amongst servers in either your own domain or in the course's domain.");
+                        }
+                    } else {
+                        $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'<br />'.&mt("No suitable server could be found amongst servers in your own domain (which is also the course's domain).");
+                    }
+                } else {
+                    $otherserver = $userdomserver;
+                }
+            }
+            if ($otherserver ne '') {
+                $switchserver = 'otherserver='.$otherserver.'&amp;role='.$rolecode;
+            }
+        }
+    }
+    return ($switchserver,$warning);
+}
+
+=pod
+
+=item * &check_release_result()
+
+Inputs:
+
+$switchwarning - Warning message if no suitable server found to host session.
+
+$switchserver - query string to append to /adm/switchserver containing lonHostID
+                and current role.
+
+Returns: HTML to display with information about requirement to switch server.
+         Either displaying warning with link to Roles/Courses screen or
+         display link to switchserver.
+
+=cut
+
+sub check_release_result {
+    my ($switchwarning,$switchserver) = @_;
+    my $output = &start_page('Selected course unavailable on this server').
+                 '<p class="LC_warning">';
+    if ($switchwarning) {
+        $output .= $switchwarning.'<br /><a href="/adm/roles">';
+        if (&show_course()) {
+            $output .= &mt('Display courses');
+        } else {
+            $output .= &mt('Display roles');
+        }
+        $output .= '</a>';
+    } elsif ($switchserver) {
+        $output .= &mt('This course requires a newer version of LON-CAPA than is installed on this server.').
+                   '<br />'.
+                   '<a href="/adm/switchserver?'.$switchserver.'">'.
+                   &mt('Switch Server').
+                   '</a>';
+    }
+    $output .= '</p>'.&end_page();
+    return $output;
+}
 
 =pod
 
+=item * &needs_coursereinit()
+
+Determine if course contents stored for user's session needs to be
+refreshed, because content has changed since "Big Hash" last tied.
+
+Check for change is made if time last checked is more than 10 minutes ago
+(by default).
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$interval (optional) - Time which may elapse (in s) between last check for content
+                       change in current course. (default: 600 s).  
+
+Returns: an array; first element is:
+
+=over 4
+
+'switch' - if content updates mean user's session
+           needs to be switched to a server running a newer LON-CAPA version
+ 
+'update' - if course session needs to be refreshed (i.e., Big Hash needs to be reloaded)
+           on current server hosting user's session                
+
+''       - if no action required.
+
+=back
+
+If first item element is 'switch':
+
+second item is $switchwarning - Warning message if no suitable server found to host session. 
+
+third item is $switchserver - query string to append to /adm/switchserver containing lonHostID
+                              and current role. 
+
+otherwise: no other elements returned.
+
 =back
 
 =cut
 
+sub needs_coursereinit {
+    my ($loncaparev,$interval) = @_;
+    return() unless ($env{'request.course.id'} && $env{'request.course.tied'});
+    my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+    my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+    my $now = time;
+    if ($interval eq '') {
+        $interval = 600;
+    }
+    if (($now-$env{'request.course.timechecked'})>$interval) {
+        my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
+        &Apache::lonnet::appenv({'request.course.timechecked'=>$now});
+        if ($lastchange > $env{'request.course.tied'}) {
+            my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
+            if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
+                my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
+                if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
+                    &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
+                                             $curr_reqd_hash{'internal.releaserequired'}});
+                    my ($switchserver,$switchwarning) =
+                        &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
+                                                $curr_reqd_hash{'internal.releaserequired'});
+                    if ($switchwarning ne '' || $switchserver ne '') {
+                        return ('switch',$switchwarning,$switchserver);
+                    }
+                }
+            }
+            return ('update');
+        }
+    }
+    return ();
+}
 
 sub update_content_constraints {
     my ($cdom,$cnum,$chome,$cid) = @_;
@@ -15419,29 +16973,30 @@ sub symb_to_docspath {
 sub captcha_display {
     my ($context,$lonhost) = @_;
     my ($output,$error);
-    my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+    my ($captcha,$pubkey,$privkey,$version) = 
+        &get_captcha_config($context,$lonhost);
     if ($captcha eq 'original') {
         $output = &create_captcha();
         unless ($output) {
             $error = 'captcha';
         }
     } elsif ($captcha eq 'recaptcha') {
-        $output = &create_recaptcha($pubkey);
+        $output = &create_recaptcha($pubkey,$version);
         unless ($output) {
             $error = 'recaptcha';
         }
     }
-    return ($output,$error,$captcha);
+    return ($output,$error,$captcha,$version);
 }
 
 sub captcha_response {
     my ($context,$lonhost) = @_;
     my ($captcha_chk,$captcha_error);
-    my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+    my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost);
     if ($captcha eq 'original') {
         ($captcha_chk,$captcha_error) = &check_captcha();
     } elsif ($captcha eq 'recaptcha') {
-        $captcha_chk = &check_recaptcha($privkey);
+        $captcha_chk = &check_recaptcha($privkey,$version);
     } else {
         $captcha_chk = 1;
     }
@@ -15450,7 +17005,7 @@ sub captcha_response {
 
 sub get_captcha_config {
     my ($context,$lonhost) = @_;
-    my ($captcha,$pubkey,$privkey,$hashtocheck);
+    my ($captcha,$pubkey,$privkey,$version,$hashtocheck);
     my $hostname = &Apache::lonnet::hostname($lonhost);
     my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
     my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
@@ -15466,6 +17021,10 @@ sub get_captcha_config {
                     }
                     if ($privkey && $pubkey) {
                         $captcha = 'recaptcha';
+                        $version = $hashtocheck->{'recaptchaversion'};
+                        if ($version ne '2') {
+                            $version = 1;
+                        }
                     } else {
                         $captcha = 'original';
                     }
@@ -15483,6 +17042,10 @@ sub get_captcha_config {
             $privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'};
             if ($privkey && $pubkey) {
                 $captcha = 'recaptcha';
+                $version = $domconfhash{$serverhomedom.'.login.recaptchaversion'};
+                if ($version ne '2') {
+                    $version = 1; 
+                }
             } else {
                 $captcha = 'original';
             }
@@ -15490,7 +17053,7 @@ sub get_captcha_config {
             $captcha = 'original';
         }
     }
-    return ($captcha,$pubkey,$privkey);
+    return ($captcha,$pubkey,$privkey,$version);
 }
 
 sub create_captcha {
@@ -15549,38 +17112,61 @@ sub check_captcha {
 }
 
 sub create_recaptcha {
-    my ($pubkey) = @_;
-    my $use_ssl;
-    if ($ENV{'SERVER_PORT'} == 443) {
-        $use_ssl = 1;
-    }
-    my $captcha = Captcha::reCAPTCHA->new;
-    return $captcha->get_options_setter({theme => 'white'})."\n".
-           $captcha->get_html($pubkey,undef,$use_ssl).
-           &mt('If either word is hard to read, [_1] will replace them.',
-               '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
-           '<br /><br />';
+    my ($pubkey,$version) = @_;
+    if ($version >= 2) {
+        return '<div class="g-recaptcha" data-sitekey="'.$pubkey.'"></div>';
+    } else {
+        my $use_ssl;
+        if ($ENV{'SERVER_PORT'} == 443) {
+            $use_ssl = 1;
+        }
+        my $captcha = Captcha::reCAPTCHA->new;
+        return $captcha->get_options_setter({theme => 'white'})."\n".
+               $captcha->get_html($pubkey,undef,$use_ssl).
+               &mt('If the text is hard to read, [_1] will replace them.',
+                   '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
+               '<br /><br />';
+    }
 }
 
 sub check_recaptcha {
-    my ($privkey) = @_;
+    my ($privkey,$version) = @_;
     my $captcha_chk;
-    my $captcha = Captcha::reCAPTCHA->new;
-    my $captcha_result =
-        $captcha->check_answer(
-                                $privkey,
-                                $ENV{'REMOTE_ADDR'},
-                                $env{'form.recaptcha_challenge_field'},
-                                $env{'form.recaptcha_response_field'},
-                              );
-    if ($captcha_result->{is_valid}) {
-        $captcha_chk = 1;
+    if ($version >= 2) {
+        my $ua = LWP::UserAgent->new;
+        $ua->timeout(10);
+        my %info = (
+                     secret   => $privkey, 
+                     response => $env{'form.g-recaptcha-response'},
+                     remoteip => $ENV{'REMOTE_ADDR'},
+                   );
+        my $response = $ua->post('https://www.google.com/recaptcha/api/siteverify',\%info);
+        if ($response->is_success)  {
+            my $data = JSON::DWIW->from_json($response->decoded_content);
+            if (ref($data) eq 'HASH') {
+                if ($data->{'success'}) {
+                    $captcha_chk = 1;
+                }
+            }
+        }
+    } else {
+        my $captcha = Captcha::reCAPTCHA->new;
+        my $captcha_result =
+            $captcha->check_answer(
+                                    $privkey,
+                                    $ENV{'REMOTE_ADDR'},
+                                    $env{'form.recaptcha_challenge_field'},
+                                    $env{'form.recaptcha_response_field'},
+                                  );
+        if ($captcha_result->{is_valid}) {
+            $captcha_chk = 1;
+        }
     }
     return $captcha_chk;
 }
 
 sub emailusername_info {
-    my @fields = ('firstname','lastname','institution','web','location','officialemail');
+    my @fields = ('firstname','lastname','institution','web','location','officialemail','id');
     my %titles = &Apache::lonlocal::texthash (
                      lastname      => 'Last Name',
                      firstname     => 'First Name',
@@ -15588,6 +17174,7 @@ sub emailusername_info {
                      location      => "School's city, state/province, country",
                      web           => "School's web address",
                      officialemail => 'E-mail address at institution (if different)',
+                     id            => 'Student/Employee ID',
                  );
     return (\@fields,\%titles);
 }
@@ -15668,11 +17255,19 @@ sub des_decrypt {
     } else {
         $cypher=new DES $keybin;
     }
-    my $plaintext=
-        $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16))));
-    $plaintext.=
-        $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16))));
-    $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) );
+    my $plaintext='';
+    my $cypherlength = length($cyphertext);
+    my $numchunks = int($cypherlength/32);
+    for (my $j=0; $j<$numchunks; $j++) {
+        my $start = $j*32;
+        my $cypherblock = substr($cyphertext,$start,32);
+        my $chunk =
+            $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,0,16))));
+        $chunk .=
+            $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,16,16))));
+        $chunk=substr($chunk,1,ord(substr($chunk,0,1)) );
+        $plaintext .= $chunk;
+    }
     return $plaintext;
 }