--- loncom/interface/loncommon.pm	2014/02/11 14:29:04	1.1173
+++ loncom/interface/loncommon.pm	2015/04/07 15:12:26	1.1215
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1173 2014/02/11 14:29:04 kruse Exp $
+# $Id: loncommon.pm,v 1.1215 2015/04/07 15:12:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -69,12 +69,15 @@ use Apache::lontexconvert();
 use Apache::lonclonecourse();
 use Apache::lonuserutils();
 use Apache::lonuserstate();
+use Apache::courseclassifier();
 use LONCAPA qw(:DEFAULT :match);
 use DateTime::TimeZone;
 use DateTime::Locale::Catalog;
 use Text::Aspell;
 use Authen::Captcha;
 use Captcha::reCAPTCHA;
+use Crypt::DES;
+use DynaLoader; # for Crypt::DES version
 
 # ---------------------------------------------- Designs
 use vars qw(%defaultdesign);
@@ -1313,8 +1316,10 @@ sub helpLatexCheatsheet {
 	  .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600)
 	  .'</span>';
     unless ($not_author) {
-        $out .= ' <span>'
-	       .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
+        $out .= '<span>'
+               .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
+               .'</span> <span>'
+               .&help_open_topic('Authoring_Multilingual_Problems',&mt('How to create problems in different languages'),$stayOnPage,undef,600)
 	       .'</span>';
     }
     $out .= '</span>'; # End cheatsheet
@@ -1752,6 +1757,241 @@ RESIZE
 
 }
 
+sub colorfuleditor_js {
+    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);
+        }
+    }
+
+
+
+// ]]>
+</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')\">";
+}
+
 =pod
 
 =head1 Excel and CSV file utility routines
@@ -2360,6 +2600,8 @@ Outputs:
 
 =item * $clientinfo
 
+=item * $clientosversion
+
 =back
 
 =back 
@@ -2379,8 +2621,9 @@ sub decode_user_agent {
     my $clientmathml='';
     my $clientunicode='0';
     my $clientmobile=0;
+    my $clientosversion='';
     for (my $i=0;$i<=$#browsertype;$i++) {
-        my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\:/,$browsertype[$i]);
+        my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\%/,$browsertype[$i]);
 	if (($httpbrowser=~/$match/i)  && ($httpbrowser!~/$notmatch/i)) {
 	    $clientbrowser=$bname;
             $httpbrowser=~/$vreg/i;
@@ -2400,7 +2643,12 @@ sub decode_user_agent {
     if ($httpbrowser=~/next/i) { $clientos='next'; }
     if (($httpbrowser=~/mac/i) ||
         ($httpbrowser=~/powerpc/i)) { $clientos='mac'; }
-    if ($httpbrowser=~/win/i) { $clientos='win'; }
+    if ($httpbrowser=~/win/i) {
+        $clientos='win';
+        if ($httpbrowser =~/Windows\s+NT\s+(\d+\.\d+)/i) {
+            $clientosversion = $1;
+        }
+    }
     if ($httpbrowser=~/embed/i) { $clientos='pda'; }
     if ($httpbrowser=~/(Android|iPod|iPad|iPhone|webOS|Blackberry|Windows Phone|Opera m(?:ob|in)|Fennec)/i) {
         $clientmobile=lc($1);
@@ -2411,7 +2659,8 @@ sub decode_user_agent {
         $clientinfo = 'chromeframe-'.$1;
     }
     return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-            $clientunicode,$clientos,$clientmobile,$clientinfo);
+            $clientunicode,$clientos,$clientmobile,$clientinfo,
+            $clientosversion);
 }
 
 ###############################################################
@@ -3747,7 +3996,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:
 
@@ -3769,6 +4018,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.
@@ -3776,7 +4030,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) {
@@ -3786,13 +4040,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);
@@ -3809,6 +4068,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) {
@@ -3820,17 +4091,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$/);
@@ -3852,9 +4166,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>';
                         }
@@ -3863,9 +4183,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();
@@ -4317,23 +4643,20 @@ sub findallcourses {
 ###############################################
 
 sub blockcheck {
-    my ($setters,$activity,$uname,$udom,$url) = @_;
+    my ($setters,$activity,$uname,$udom,$url,$is_course) = @_;
 
-    if (!defined($udom)) {
+    if (defined($udom) && defined($uname)) {
+        # If uname and udom are for a course, check for blocks in the course.
+        if (($is_course) || (&Apache::lonnet::is_course($udom,$uname))) {
+            my ($startblock,$endblock,$triggerblock) =
+                &get_blocks($setters,$activity,$udom,$uname,$url);
+            return ($startblock,$endblock,$triggerblock);
+        }
+    } else {
         $udom = $env{'user.domain'};
-    }
-    if (!defined($uname)) {
         $uname = $env{'user.name'};
     }
 
-    # If uname and udom are for a course, check for blocks in the course.
-
-    if (&Apache::lonnet::is_course($udom,$uname)) {
-        my ($startblock,$endblock,$triggerblock) = 
-            &get_blocks($setters,$activity,$udom,$uname,$url);
-        return ($startblock,$endblock,$triggerblock);
-    }
-
     my $startblock = 0;
     my $endblock = 0;
     my $triggerblock = '';
@@ -4343,7 +4666,8 @@ sub blockcheck {
     # boards, chat or groups, check for blocking in current course only.
 
     if (($activity eq 'boards' || $activity eq 'chat' ||
-         $activity eq 'groups') && ($env{'request.course.id'})) {
+         $activity eq 'groups' || $activity eq 'printout') &&
+        ($env{'request.course.id'})) {
         foreach my $key (keys(%live_courses)) {
             if ($key ne $env{'request.course.id'}) {
                 delete($live_courses{$key});
@@ -4607,12 +4931,12 @@ sub parse_block_record {
 }
 
 sub blocking_status {
-    my ($activity,$uname,$udom,$url) = @_;
+    my ($activity,$uname,$udom,$url,$is_course) = @_;
     my %setters;
 
 # check for active blocking
     my ($startblock,$endblock,$triggerblock) = 
-        &blockcheck(\%setters,$activity,$uname,$udom,$url);
+        &blockcheck(\%setters,$activity,$uname,$udom,$url,$is_course);
     my $blocked = 0;
     if ($startblock && $endblock) {
         $blocked = 1;
@@ -4667,13 +4991,13 @@ 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 $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip;
 
     my $name;
     foreach my $pattern (split(',',$acc)) {
@@ -4769,23 +5093,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'};
+                                        }
                                     }
                                 }
                             }
@@ -5149,7 +5479,10 @@ sub bodytag {
     @design{keys(%$addentries)} = @$addentries{keys(%$addentries)}; 
 
  # role and realm
-    my ($role,$realm) = split(/\./,$env{'request.role'},2);
+    my ($role,$realm) = split(m{\./},$env{'request.role'},2);
+    if ($realm) {
+        $realm = '/'.$realm;
+    }
     if ($role  eq 'ca') {
         my ($rdom,$rname) = ($realm =~ m{^/($match_domain)/($match_username)$});
         $realm = &plainname($rname,$rdom);
@@ -6480,6 +6813,7 @@ 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 {
@@ -6497,7 +6831,12 @@ table.LC_edit_problem_header_title {
 
 div.LC_edit_problem_discards {
   float: left;
-  padding-bottom: 5px;
+}
+
+div.LC_edit_actionbar {
+    margin: -5px 0px 0px 0px !important;
+    background-color: $sidebg;
+    height: 31px;
 }
 
 div.LC_edit_problem_saves {
@@ -6518,6 +6857,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;
@@ -6605,6 +6948,10 @@ fieldset {
   /* overflow: hidden; */
 }
 
+article.geogebraweb div {
+    margin: 0;
+}
+
 fieldset > legend {
   font-weight: bold;
   padding: 0 5px 0 5px;
@@ -6632,7 +6979,6 @@ fieldset > legend {
 ol.LC_primary_menu {
   margin: 0;
   padding: 0;
-  background-color: $pgbg_or_bgcolor;
 }
 
 ol#LC_PathBreadcrumbs {
@@ -6644,23 +6990,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;
@@ -6669,15 +7040,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 {
@@ -6685,6 +7062,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;
@@ -7296,7 +7678,10 @@ sub headtag {
 	'<head>'.
 	&font_settings($args);
 
-    my $inhibitprint = &print_suppression();
+    my $inhibitprint;
+    if ($args->{'print_suppress'}) {
+        $inhibitprint = &print_suppression();
+    }
 
     if (!$args->{'frameset'}) {
 	$result .= &Apache::lonhtmlcommon::htmlareaheaders();
@@ -7336,6 +7721,81 @@ 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;
+                                    }
+                                }
+                                $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';
@@ -7422,7 +7882,7 @@ sub print_suppression {
         }
         my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
         my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
-        my $blocked = &blocking_status('printout',$cnum,$cdom);
+        my $blocked = &blocking_status('printout',$cnum,$cdom,undef,1);
         if ($blocked) {
             my $checkrole = "cm./$cdom/$cnum";
             if ($env{'request.course.sec'} ne '') {
@@ -7654,9 +8114,13 @@ function set_wishlistlink(title, path) {
         title = document.title;
         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');
 }
@@ -7699,12 +8163,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>
@@ -8306,7 +8771,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;
@@ -8963,10 +9428,10 @@ sub excess_filesize_warning {
     }
     $disk_quota = int($disk_quota * 1000);
     if (($current_disk_usage + $filesize) > $disk_quota) {
-        return '<p><span class="LC_warning">'.
+        return '<p class="LC_warning">'.
                 &mt("Unable to $action [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.",
-                    '<span class="LC_filename">'.$filename.'</span>',$filesize).'</span>'.
-               '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+                    '<span class="LC_filename">'.$filename.'</span>',$filesize).'</p>'.
+               '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
                             $disk_quota,$current_disk_usage).
                '</p>';
     }
@@ -9419,7 +9884,14 @@ sub personal_data_fieldtitles {
 
 sub sorted_inst_types {
     my ($dom) = @_;
-    my ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+    my ($usertypes,$order);
+    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+    if (ref($domdefaults{'inststatus'}) eq 'HASH') {
+        $usertypes = $domdefaults{'inststatus'}{'inststatustypes'};
+        $order = $domdefaults{'inststatus'}{'inststatusorder'};
+    } else {
+        ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+    }
     my $othertitle = &mt('All users');
     if ($env{'request.course.id'}) {
         $othertitle  = &mt('Any users');
@@ -9882,7 +10354,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)");
@@ -10913,11 +11393,11 @@ sub check_for_upload {
                     if ($currsize < $filesize) {
                         my $extra = $filesize - $currsize;
                         if (($current_disk_usage + $extra) > $disk_quota) {
-                            my $msg = '<span class="LC_error">'.
+                            my $msg = '<p class="LC_warning">'.
                                       &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded if existing (smaller) file with same name (size = [_3] kilobytes) is replaced.',
-                                          '<span class="LC_filename">'.$fname.'</span>',$filesize,$currsize).'</span>'.
-                                      '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
-                                                   $disk_quota,$current_disk_usage);
+                                          '<span class="LC_filename">'.$fname.'</span>',$filesize,$currsize).'</p>'.
+                                      '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+                                                   $disk_quota,$current_disk_usage).'</p>';
                             return ('will_exceed_quota',$msg);
                         }
                     }
@@ -10926,21 +11406,21 @@ sub check_for_upload {
         }
     }
     if (($current_disk_usage + $filesize) > $disk_quota){
-        my $msg = '<span class="LC_error">'.
-                &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.','<span class="LC_filename">'.$fname.'</span>',$filesize).'</span>'.
-                  '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage);
+        my $msg = '<p class="LC_warning">'.
+                &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.','<span class="LC_filename">'.$fname.'</span>',$filesize).'</p>'.
+                  '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage).'</p>';
         return ('will_exceed_quota',$msg);
     } elsif ($found_file) {
         if ($locked_file) {
-            my $msg = '<span class="LC_error">';
+            my $msg = '<p class="LC_warning">';
             $msg .= &mt('Unable to upload [_1]. A locked file by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>','<span class="LC_filename">'.$port_path.$env{'form.currentpath'}.'</span>');
-            $msg .= '</span><br />';
+            $msg .= '</p>';
             $msg .= &mt('You will be able to rename or delete existing [_1] after a grade has been assigned.','<span class="LC_filename">'.$fname.'</span>');
             return ('file_locked',$msg);
         } else {
-            my $msg = '<span class="LC_error">';
+            my $msg = '<p class="LC_error">';
             $msg .= &mt(' A file by that name: [_1] was found in [_2].','<span class="LC_filename">'.$fname.'</span>',$port_path.$env{'form.currentpath'});
-            $msg .= '</span>';
+            $msg .= '</p>';
             return ('existingfile',$msg);
         }
     }
@@ -11038,7 +11518,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",
@@ -11060,13 +11540,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;
+                }
             }
         }
     }
@@ -11080,7 +11583,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';
@@ -11265,7 +11767,7 @@ sub decompress_uploaded_file {
 sub process_decompression {
     my ($docudom,$docuname,$file,$destination,$dir_root,$hiddenelem) = @_;
     my ($dir,$error,$warning,$output);
-    if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/) {
+    if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/i) {
         $error = &mt('Filename not a supported archive file type.').
                  '<br />'.&mt('Filename should end with one of: [_1].',
                               '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz');
@@ -13656,6 +14158,12 @@ sub assign_category_rows {
     return $text;
 }
 
+=pod
+
+=back
+
+=cut
+
 ############################################################
 ############################################################
 
@@ -14428,7 +14936,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 {
@@ -14486,6 +14994,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'}
@@ -14499,8 +15018,8 @@ sub init_user_environment {
     }
 # ------------------------------------ Check browser type and MathML capability
 
-    my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-        $clientunicode,$clientos,$clientmobile,$clientinfo) = &decode_user_agent($r);
+    my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,$clientunicode,
+        $clientos,$clientmobile,$clientinfo,$clientosversion) = &decode_user_agent($r);
 
 # ------------------------------------------------------------- Get environment
 
@@ -14533,6 +15052,7 @@ sub init_user_environment {
 	     "browser.os"         => $clientos,
              "browser.mobile"     => $clientmobile,
              "browser.info"       => $clientinfo,
+             "browser.osversion"  => $clientosversion,
 	     "server.domain"      => $Apache::lonnet::perlvar{'lonDefDomain'},
 	     "request.course.fn"  => '',
 	     "request.course.uri" => '',
@@ -14675,36 +15195,737 @@ sub clean_symb {
     return ($symb,$enc);
 }
 
-sub build_release_hashes {
-    my ($checkparms,$checkresponsetypes,$checkcrstypes,$anonsurvey,$randomizetry) = @_;
-    return unless((ref($checkparms) eq 'HASH') && (ref($checkresponsetypes) eq 'HASH') &&
-                  (ref($checkcrstypes) eq 'HASH') && (ref($anonsurvey) eq 'HASH') &&
-                  (ref($randomizetry) eq 'HASH'));
-    foreach my $key (keys(%Apache::lonnet::needsrelease)) {
-        my ($item,$name,$value) = split(/:/,$key);
-        if ($item eq 'parameter') {
-            if (ref($checkparms->{$name}) eq 'ARRAY') {
-                unless(grep(/^\Q$name\E$/,@{$checkparms->{$name}})) {
-                    push(@{$checkparms->{$name}},$value);
-                }
+############################################################
+############################################################
+
+=pod
+
+=head1 Routines for building display used to search for courses
+
+
+=over 4
+
+=item * &build_filters()
+
+Create markup for a table used to set filters to use when selecting
+courses in a domain.  Used by lonpickcourse.pm, lonmodifycourse.pm
+and quotacheck.pl
+
+
+Inputs:
+
+filterlist - anonymous array of fields to include as potential filters 
+
+crstype - course type
+
+roleelement - fifth arg in selectcourse_link() populates fifth arg in javascript: opencrsbrowser() function, used
+              to pop-open a course selector (will contain "extra element"). 
+
+multelement - if multiple course selections will be allowed, this will be a hidden form element: name: multiple; value: 1
+
+filter - anonymous hash of criteria and their values
+
+action - form action
+
+numfiltersref - ref to scalar (count of number of elements in institutional codes -- e.g., 4 for year, semester, department, and number)
+
+caller - caller context (e.g., set to 'modifycourse' when routine is called from lonmodifycourse.pm)
+
+cloneruname - username of owner of new course who wants to clone
+
+clonerudom - domain of owner of new course who wants to clone
+
+typeelem - text to use for left column in row containing course type (i.e., Course, Community or Course/Community) 
+
+codetitlesref - reference to array of titles of components in institutional codes (official courses)
+
+codedom - domain
+
+formname - value of form element named "form". 
+
+fixeddom - domain, if fixed.
+
+prevphase - value to assign to form element named "phase" when going back to the previous screen  
+
+cnameelement - name of form element in form on opener page which will receive title of selected course 
+
+cnumelement - name of form element in form on opener page which will receive courseID  of selected course
+
+cdomelement - name of form element in form on opener page which will receive domain of selected course
+
+setroles - includes access constraint identifier when setting a roles-based condition for acces to a portfolio file
+
+clonetext - hidden form elements containing list of courses cloneable by intended course owner when DC creates a course
+
+clonewarning - warning message about missing information for intended course owner when DC creates a course
+
+
+Returns: $output - HTML for display of search criteria, and hidden form elements.
+
+
+Side Effects: None
+
+=cut
+
+# ---------------------------------------------- search for courses based on last activity etc.
+
+sub build_filters {
+    my ($filterlist,$crstype,$roleelement,$multelement,$filter,$action,
+        $numtitlesref,$caller,$cloneruname,$clonerudom,$typeelement,
+        $codetitlesref,$codedom,$formname,$fixeddom,$prevphase,
+        $cnameelement,$cnumelement,$cdomelement,$setroles,
+        $clonetext,$clonewarning) = @_;
+    my ($list,$jscript);
+    my $onchange = 'javascript:updateFilters(this)';
+    my ($domainselectform,$sincefilterform,$createdfilterform,
+        $ownerdomselectform,$persondomselectform,$instcodeform,
+        $typeselectform,$instcodetitle);
+    if ($formname eq '') {
+        $formname = $caller;
+    }
+    foreach my $item (@{$filterlist}) {
+        unless (($item eq 'descriptfilter') || ($item eq 'instcodefilter') ||
+                ($item eq 'sincefilter') || ($item eq 'createdfilter')) {
+            if ($item eq 'domainfilter') {
+                $filter->{$item} = &LONCAPA::clean_domain($filter->{$item});
+            } elsif ($item eq 'coursefilter') {
+                $filter->{$item} = &LONCAPA::clean_courseid($filter->{$item});
+            } elsif ($item eq 'ownerfilter') {
+                $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+            } elsif ($item eq 'ownerdomfilter') {
+                $filter->{'ownerdomfilter'} =
+                    &LONCAPA::clean_domain($filter->{$item});
+                $ownerdomselectform = &select_dom_form($filter->{'ownerdomfilter'},
+                                                       'ownerdomfilter',1);
+            } elsif ($item eq 'personfilter') {
+                $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+            } elsif ($item eq 'persondomfilter') {
+                $persondomselectform = &select_dom_form($filter->{'persondomfilter'},
+                                                        'persondomfilter',1);
             } else {
-                push(@{$checkparms->{$name}},$value);
+                $filter->{$item} =~ s/\W//g;
             }
-        } elsif ($item eq 'resourcetag') {
-            if ($name eq 'responsetype') {
-                $checkresponsetypes->{$value} = $Apache::lonnet::needsrelease{$key}
+            if (!$filter->{$item}) {
+                $filter->{$item} = '';
             }
-        } elsif ($item eq 'course') {
-            if ($name eq 'crstype') {
-                $checkcrstypes->{$value} = $Apache::lonnet::needsrelease{$key};
+        }
+        if ($item eq 'domainfilter') {
+            my $allow_blank = 1;
+            if ($formname eq 'portform') {
+                $allow_blank=0;
+            } elsif ($formname eq 'studentform') {
+                $allow_blank=0;
             }
+            if ($fixeddom) {
+                $domainselectform = '<input type="hidden" name="domainfilter"'.
+                                    ' value="'.$codedom.'" />'.
+                                    &Apache::lonnet::domain($codedom,'description');
+            } else {
+                $domainselectform = &select_dom_form($filter->{$item},
+                                                     'domainfilter',
+                                                      $allow_blank,'',$onchange);
+            }
+        } else {
+            $list->{$item} = &HTML::Entities::encode($filter->{$item},'<>&"');
+        }
+    }
+
+    # last course activity filter and selection
+    $sincefilterform = &timebased_select_form('sincefilter',$filter);
+
+    # course created filter and selection
+    if (exists($filter->{'createdfilter'})) {
+        $createdfilterform = &timebased_select_form('createdfilter',$filter);
+    }
+
+    my %lt = &Apache::lonlocal::texthash(
+                'cac' => "$crstype Activity",
+                'ccr' => "$crstype Created",
+                'cde' => "$crstype Title",
+                'cdo' => "$crstype Domain",
+                'ins' => 'Institutional Code',
+                'inc' => 'Institutional Categorization',
+                'cow' => "$crstype Owner/Co-owner",
+                'cop' => "$crstype Personnel Includes",
+                'cog' => 'Type',
+             );
+
+    if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+        my $typeval = 'Course';
+        if ($crstype eq 'Community') {
+            $typeval = 'Community';
+        }
+        $typeselectform = '<input type="hidden" name="type" value="'.$typeval.'" />';
+    } else {
+        $typeselectform =  '<select name="type" size="1"';
+        if ($onchange) {
+            $typeselectform .= ' onchange="'.$onchange.'"';
+        }
+        $typeselectform .= '>'."\n";
+        foreach my $posstype ('Course','Community') {
+            $typeselectform.='<option value="'.$posstype.'"'.
+                ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".&mt($posstype)."</option>\n";
+        }
+        $typeselectform.="</select>";
+    }
+
+    my ($cloneableonlyform,$cloneabletitle);
+    if (exists($filter->{'cloneableonly'})) {
+        my $cloneableon = '';
+        my $cloneableoff = ' checked="checked"';
+        if ($filter->{'cloneableonly'}) {
+            $cloneableon = $cloneableoff;
+            $cloneableoff = '';
+        }
+        $cloneableonlyform = '<span class="LC_nobreak"><label><input type="radio" name="cloneableonly" value="1" '.$cloneableon.'/>&nbsp;'.&mt('Required').'</label>'.('&nbsp;'x3).'<label><input type="radio" name="cloneableonly" value="" '.$cloneableoff.' />&nbsp;'.&mt('No restriction').'</label></span>';
+        if ($formname eq 'ccrs') {
+            $cloneabletitle = &mt('Cloneable for [_1]',$cloneruname.':'.$clonerudom);
+        } else {
+            $cloneabletitle = &mt('Cloneable by you');
+        }
+    }
+    my $officialjs;
+    if ($crstype eq 'Course') {
+        if (exists($filter->{'instcodefilter'})) {
+#            if (($fixeddom) || ($formname eq 'requestcrs') ||
+#                ($formname eq 'modifycourse') || ($formname eq 'filterpicker')) {
+            if ($codedom) { 
+                $officialjs = 1;
+                ($instcodeform,$jscript,$$numtitlesref) =
+                    &Apache::courseclassifier::instcode_selectors($codedom,'filterpicker',
+                                                                  $officialjs,$codetitlesref);
+                if ($jscript) {
+                    $jscript = '<script type="text/javascript">'."\n".
+                               '// <![CDATA['."\n".
+                               $jscript."\n".
+                               '// ]]>'."\n".
+                               '</script>'."\n";
+                }
+            }
+            if ($instcodeform eq '') {
+                $instcodeform =
+                    '<input type="text" name="instcodefilter" size="10" value="'.
+                    $list->{'instcodefilter'}.'" />';
+                $instcodetitle = $lt{'ins'};
+            } else {
+                $instcodetitle = $lt{'inc'};
+            }
+            if ($fixeddom) {
+                $instcodetitle .= '<br />('.$codedom.')';
+            }
+        }
+    }
+    my $output = qq|
+<form method="post" name="filterpicker" action="$action">
+<input type="hidden" name="form" value="$formname" />
+|;
+    if ($formname eq 'modifycourse') {
+        $output .= '<input type="hidden" name="phase" value="courselist" />'."\n".
+                   '<input type="hidden" name="prevphase" value="'.
+                   $prevphase.'" />'."\n";
+    } 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="'.
+                          $cnameelement.'" />';
+        }
+        $output .= qq|
+<input type="hidden" name="cnumelement" value="$cnumelement" />
+<input type="hidden" name="cdomelement" value="$cdomelement" />
+$name_input
+$roleelement
+$multelement
+$typeelement
+|;
+        if ($formname eq 'portform') {
+            $output .= '<input type="hidden" name="setroles" value="'.$setroles.'" />'."\n";
         }
     }
-    ($anonsurvey->{major},$anonsurvey->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:anonsurvey'});
-    ($randomizetry->{major},$randomizetry->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:randomizetry'});
+    if ($fixeddom) {
+        $output .= '<input type="hidden" name="fixeddom" value="'.$fixeddom.'" />'."\n";
+    }
+    $output .= "<br />\n".&Apache::lonhtmlcommon::start_pick_box();
+    if ($sincefilterform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cac'})
+                  .$sincefilterform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($createdfilterform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'ccr'})
+                  .$createdfilterform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($domainselectform) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cdo'})
+                  .$domainselectform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($typeselectform) {
+        if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+            $output .= $typeselectform;
+        } else {
+            $output .= &Apache::lonhtmlcommon::row_title($lt{'cog'})
+                      .$typeselectform
+                      .&Apache::lonhtmlcommon::row_closure();
+        }
+    }
+    if ($instcodeform) {
+        $output .= &Apache::lonhtmlcommon::row_title($instcodetitle)
+                  .$instcodeform
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'ownerfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cow'}).
+                   '<table><tr><td>'.&mt('Username').'<br />'.
+                   '<input type="text" name="ownerfilter" size="20" value="'.
+                   $list->{'ownerfilter'}.'" /></td><td>'.&mt('Domain').'<br />'.
+                   $ownerdomselectform.'</td></tr></table>'.
+                   &Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'personfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cop'}).
+                   '<table><tr><td>'.&mt('Username').'<br />'.
+                   '<input type="text" name="personfilter" size="20" value="'.
+                   $list->{'personfilter'}.'" /></td><td>'.&mt('Domain').'<br />'.
+                   $persondomselectform.'</td></tr></table>'.
+                   &Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'coursefilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title(&mt('LON-CAPA course ID'))
+                  .'<input type="text" name="coursefilter" size="25" value="'
+                  .$list->{'coursefilter'}.'" />'
+                  .&Apache::lonhtmlcommon::row_closure();
+    }
+    if ($cloneableonlyform) {
+        $output .= &Apache::lonhtmlcommon::row_title($cloneabletitle).
+                   $cloneableonlyform.&Apache::lonhtmlcommon::row_closure();
+    }
+    if (exists($filter->{'descriptfilter'})) {
+        $output .= &Apache::lonhtmlcommon::row_title($lt{'cde'})
+                  .'<input type="text" name="descriptfilter" size="40" value="'
+                  .$list->{'descriptfilter'}.'" />'
+                  .&Apache::lonhtmlcommon::row_closure(1);
+    }
+    $output .= &Apache::lonhtmlcommon::end_pick_box().'<p>'.$clonetext."\n".
+               '<input type="hidden" name="updater" value="" />'."\n".
+               '<input type="submit" name="gosearch" value="'.
+               &mt('Search').'" /></p>'."\n".'</form>'."\n".'<hr />'."\n";
+    return $jscript.$clonewarning.$output;
+}
+
+=pod 
+
+=item * &timebased_select_form()
+
+Create markup for a dropdown list used to select a time-based
+filter e.g., Course Activity, Course Created, when searching for courses
+or communities
+
+Inputs:
+
+item - name of form element (sincefilter or createdfilter)
+
+filter - anonymous hash of criteria and their values
+
+Returns: HTML for a select box contained a blank, then six time selections,
+         with value set in incoming form variables currently selected. 
+
+Side Effects: None
+
+=cut
+
+sub timebased_select_form {
+    my ($item,$filter) = @_;
+    if (ref($filter) eq 'HASH') {
+        $filter->{$item} =~ s/[^\d-]//g;
+        if (!$filter->{$item}) { $filter->{$item}=-1; }
+        return &select_form(
+                            $filter->{$item},
+                            $item,
+                            {      '-1' => '',
+                                '86400' => &mt('today'),
+                               '604800' => &mt('last week'),
+                              '2592000' => &mt('last month'),
+                              '7776000' => &mt('last three months'),
+                             '15552000' => &mt('last six months'),
+                             '31104000' => &mt('last year'),
+                    'select_form_order' =>
+                           ['-1','86400','604800','2592000','7776000',
+                            '15552000','31104000']});
+    }
+}
+
+=pod
+
+=item * &js_changer()
+
+Create script tag containing Javascript used to submit course search form
+when course type or domain is changed, and also to hide 'Searching ...' on
+page load completion for page showing search result.
+
+Inputs: None
+
+Returns: markup containing updateFilters() and hideSearching() javascript functions. 
+
+Side Effects: None
+
+=cut
+
+sub js_changer {
+    return <<ENDJS;
+<script type="text/javascript">
+// <![CDATA[
+function updateFilters(caller) {
+    if (typeof(caller) != "undefined") {
+        document.filterpicker.updater.value = caller.name;
+    }
+    document.filterpicker.submit();
+}
+
+function hideSearching() {
+    if (document.getElementById('searching')) {
+        document.getElementById('searching').style.display = 'none';
+    }
     return;
 }
 
+// ]]>
+</script>
+
+ENDJS
+}
+
+=pod
+
+=item * &search_courses()
+
+Process selected filters form course search form and pass to lonnet::courseiddump
+to retrieve a hash for which keys are courseIDs which match the selected filters.
+
+Inputs:
+
+dom - domain being searched 
+
+type - course type ('Course' or 'Community' or '.' if any).
+
+filter - anonymous hash of criteria and their values
+
+numtitles - for institutional codes - number of categories
+
+cloneruname - optional username of new course owner
+
+clonerudom - optional domain of new course owner
+
+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).
+
+
+Returns: %courses - hash of courses satisfying search criteria, keys = course IDs, values are corresponding colon-separated escaped description, institutional code, owner and type.
+
+
+Side Effects: None
+
+=cut
+
+
+sub search_courses {
+    my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles) = @_;
+    my (%courses,%showcourses,$cloner);
+    if (($filter->{'ownerfilter'} ne '') ||
+        ($filter->{'ownerdomfilter'} ne '')) {
+        $filter->{'combownerfilter'} = $filter->{'ownerfilter'}.':'.
+                                       $filter->{'ownerdomfilter'};
+    }
+    foreach my $item ('descriptfilter','coursefilter','combownerfilter') {
+        if (!$filter->{$item}) {
+            $filter->{$item}='.';
+        }
+    }
+    my $now = time;
+    my $timefilter =
+       ($filter->{'sincefilter'}==-1?1:$now-$filter->{'sincefilter'});
+    my ($createdbefore,$createdafter);
+    if (($filter->{'createdfilter'} ne '') && ($filter->{'createdfilter'} !=-1)) {
+        $createdbefore = $now;
+        $createdafter = $now-$filter->{'createdfilter'};
+    }
+    my ($instcodefilter,$regexpok);
+    if ($numtitles) {
+        if ($env{'form.official'} eq 'on') {
+            $instcodefilter =
+                &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+            $regexpok = 1;
+        } elsif ($env{'form.official'} eq 'off') {
+            $instcodefilter = &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+            unless ($instcodefilter eq '') {
+                $regexpok = -1;
+            }
+        }
+    } else {
+        $instcodefilter = $filter->{'instcodefilter'};
+    }
+    if ($instcodefilter eq '') { $instcodefilter = '.'; }
+    if ($type eq '') { $type = '.'; }
+
+    if (($clonerudom ne '') && ($cloneruname ne '')) {
+        $cloner = $cloneruname.':'.$clonerudom;
+    }
+    %courses = &Apache::lonnet::courseiddump($dom,
+                                             $filter->{'descriptfilter'},
+                                             $timefilter,
+                                             $instcodefilter,
+                                             $filter->{'combownerfilter'},
+                                             $filter->{'coursefilter'},
+                                             undef,undef,$type,$regexpok,undef,undef,
+                                             undef,undef,$cloner,$env{'form.cc_clone'},
+                                             $filter->{'cloneableonly'},
+                                             $createdbefore,$createdafter,undef,
+                                             $domcloner);
+    if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) {
+        my $ccrole;
+        if ($type eq 'Community') {
+            $ccrole = 'co';
+        } else {
+            $ccrole = 'cc';
+        }
+        my %rolehash = &Apache::lonnet::get_my_roles($filter->{'personfilter'},
+                                                     $filter->{'persondomfilter'},
+                                                     'userroles',undef,
+                                                     [$ccrole,'in','ad','ep','ta','cr'],
+                                                     $dom);
+        foreach my $role (keys(%rolehash)) {
+            my ($cnum,$cdom,$courserole) = split(':',$role);
+            my $cid = $cdom.'_'.$cnum;
+            if (exists($courses{$cid})) {
+                if (ref($courses{$cid}) eq 'HASH') {
+                    if (ref($courses{$cid}{roles}) eq 'ARRAY') {
+                        if (!grep(/^\Q$courserole\E$/,@{$courses{$cid}{roles}})) {
+                            push (@{$courses{$cid}{roles}},$courserole);
+                        }
+                    } else {
+                        $courses{$cid}{roles} = [$courserole];
+                    }
+                    $showcourses{$cid} = $courses{$cid};
+                }
+            }
+        }
+        %courses = %showcourses;
+    }
+    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) = @_;
     my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
@@ -14898,7 +16119,7 @@ sub captcha_display {
             $error = 'recaptcha';
         }
     }
-    return ($output,$error);
+    return ($output,$error,$captcha);
 }
 
 sub captcha_response {
@@ -14974,8 +16195,9 @@ sub create_captcha {
         if (-e $Apache::lonnet::perlvar{'lonCaptchaDir'}.'/'.$md5sum.'.png') {
             $output = '<input type="hidden" name="crypt" value="'.$md5sum.'" />'."\n".
                       &mt('Type in the letters/numbers shown below').'&nbsp;'.
-                     '<input type="text" size="5" name="code" value="" /><br />'.
-                     '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
+                      '<input type="text" size="5" name="code" value="" autocomplete="off" />'.
+                      '<br />'.
+                      '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
             last;
         }
     }
@@ -15023,7 +16245,7 @@ sub create_recaptcha {
     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.',
+           &mt('If the text is hard to read, [_1] will replace them.',
                '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
            '<br /><br />';
 }
@@ -15045,6 +16267,19 @@ sub check_recaptcha {
     return $captcha_chk;
 }
 
+sub emailusername_info {
+    my @fields = ('firstname','lastname','institution','web','location','officialemail');
+    my %titles = &Apache::lonlocal::texthash (
+                     lastname      => 'Last Name',
+                     firstname     => 'First Name',
+                     institution   => 'School/college/university',
+                     location      => "School's city, state/province, country",
+                     web           => "School's web address",
+                     officialemail => 'E-mail address at institution (if different)',
+                 );
+    return (\@fields,\%titles);
+}
+
 sub cleanup_html {
     my ($incoming) = @_;
     my $outgoing;
@@ -15067,11 +16302,67 @@ sub cleanup_html {
     return $outgoing;
 }
 
-=pod
-
-=back
+# Checks for critical messages and returns a redirect url if one exists.
+# $interval indicates how often to check for messages.
+sub critical_redirect {
+    my ($interval) = @_;
+    if ((time-$env{'user.criticalcheck.time'})>$interval) {
+        my @what=&Apache::lonnet::dump('critical', $env{'user.domain'}, 
+                                        $env{'user.name'});
+        &Apache::lonnet::appenv({'user.criticalcheck.time'=>time});
+        my $redirecturl;
+        if ($what[0]) {
+	    if (($what[0] ne 'con_lost') && ($what[0]!~/^error\:/)) {
+	        $redirecturl='/adm/email?critical=display';
+	        my $url=&Apache::lonnet::absolute_url().$redirecturl;
+                return (1, $url);
+            }
+        }
+    } 
+    return ();
+}
 
-=cut
+# Use:
+#   my $answer=reply("encrypt:passwd:$udom:$uname:$upass",$tryserver);
+#
+##################################################
+#          password associated functions         #
+##################################################
+sub des_keys {
+    # Make a new key for DES encryption.
+    # Each key has two parts which are returned separately.
+    # Please note:  Each key must be passed through the &hex function
+    # before it is output to the web browser.  The hex versions cannot
+    # be used to decrypt.
+    my @hexstr=('0','1','2','3','4','5','6','7',
+                '8','9','a','b','c','d','e','f');
+    my $lkey='';
+    for (0..7) {
+        $lkey.=$hexstr[rand(15)];
+    }
+    my $ukey='';
+    for (0..7) {
+        $ukey.=$hexstr[rand(15)];
+    }
+    return ($lkey,$ukey);
+}
+
+sub des_decrypt {
+    my ($key,$cyphertext) = @_;
+    my $keybin=pack("H16",$key);
+    my $cypher;
+    if ($Crypt::DES::VERSION>=2.03) {
+        $cypher=new Crypt::DES $keybin;
+    } 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)) );
+    return $plaintext;
+}
 
 1;
 __END__;