--- loncom/interface/loncommon.pm	2013/05/10 16:09:58	1.1127
+++ loncom/interface/loncommon.pm	2014/04/24 13:26:23	1.1186
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1127 2013/05/10 16:09:58 raeburn Exp $
+# $Id: loncommon.pm,v 1.1186 2014/04/24 13:26:23 kruse 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
@@ -1380,32 +1385,38 @@ sub top_nav_help {
     $text = &mt($text);
     my $stay_on_page = 1;
 
-    my $link = ($stay_on_page) ? "javascript:helpMenu('display')"
-	                     : "javascript:helpMenu('open')";
-    my $banner_link = &update_help_link(undef,undef,undef,undef,$stay_on_page);
-
+    my ($link,$banner_link);
+    unless ($env{'request.noversionuri'} =~ m{^/adm/helpmenu}) {
+        $link = ($stay_on_page) ? "javascript:helpMenu('display')"
+	                         : "javascript:helpMenu('open')";
+        $banner_link = &update_help_link(undef,undef,undef,undef,$stay_on_page);
+    }
     my $title = &mt('Get help');
-
-    return <<"END";
+    if ($link) {
+        return <<"END";
 $banner_link
- <a href="$link" title="$title">$text</a>
+<a href="$link" title="$title">$text</a>
 END
+    } else {
+        return '&nbsp;'.$text.'&nbsp;';
+    }
 }
 
 sub help_menu_js {
-    my ($text) = @_;
+    my ($httphost) = @_;
     my $stayOnPage = 1;
     my $width = 620;
     my $height = 600;
     my $helptopic=&general_help();
-    my $details_link = '/adm/help/'.$helptopic.'.hlp';
+    my $details_link = $httphost.'/adm/help/'.$helptopic.'.hlp';
     my $nothing=&Apache::lonhtmlcommon::javascript_nothing();
     my $start_page =
         &Apache::loncommon::start_page('Help Menu', undef,
 				       {'frameset'    => 1,
 					'js_ready'    => 1,
+                                        'use_absolute' => $httphost,
 					'add_entries' => {
-					    'border' => '0',
+					    'border' => '0', 
 					    'rows'   => "110,*",},});
     my $end_page =
         &Apache::loncommon::end_page({'frameset' => 1,
@@ -1435,9 +1446,10 @@ function helpMenu(target) {
     return;
 }
 function writeHelp(caller) {
-    caller.document.writeln('$start_page\\n<frame name="bannerframe" src="'+banner_link+'" />\\n<frame name="bodyframe" src="$details_link" />\\n$end_page')
-    caller.document.close()
-    caller.focus()
+    caller.document.writeln('$start_page\\n<frame name="bannerframe" src="'+banner_link+'" marginwidth="0" marginheight="0" frameborder="0">\\n');
+    caller.document.writeln('<frame name="bodyframe" src="$details_link" marginwidth="0" marginheight="0" frameborder="0">\\n$end_page');
+    caller.document.close();
+    caller.focus();
 }
 // END LON-CAPA Internal -->
 // ]]>
@@ -1749,8 +1761,6 @@ RESIZE
 
 =head1 Excel and CSV file utility routines
 
-=over 4
-
 =cut
 
 ###############################################################
@@ -1758,6 +1768,8 @@ RESIZE
 
 =pod
 
+=over 4
+
 =item * &csv_translate($text) 
 
 Translate $text to allow it to be output as a 'comma separated values' 
@@ -2349,6 +2361,10 @@ Outputs:
 
 =item * $clientos
 
+=item * $clientmobile
+
+=item * $clientinfo
+
 =back
 
 =back 
@@ -2367,6 +2383,7 @@ sub decode_user_agent {
     my $clientversion='0';
     my $clientmathml='';
     my $clientunicode='0';
+    my $clientmobile=0;
     for (my $i=0;$i<=$#browsertype;$i++) {
         my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\:/,$browsertype[$i]);
 	if (($httpbrowser=~/$match/i)  && ($httpbrowser!~/$notmatch/i)) {
@@ -2378,6 +2395,7 @@ sub decode_user_agent {
 	}
     }
     my $clientos='unknown';
+    my $clientinfo;
     if (($httpbrowser=~/linux/i) ||
         ($httpbrowser=~/unix/i) ||
         ($httpbrowser=~/ux/i) ||
@@ -2389,8 +2407,16 @@ sub decode_user_agent {
         ($httpbrowser=~/powerpc/i)) { $clientos='mac'; }
     if ($httpbrowser=~/win/i) { $clientos='win'; }
     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);
+    }
+    if ($httpbrowser=~ m{Firefox/(\d+\.\d+)}) {
+        $clientinfo = 'firefox-'.$1;
+    } elsif ($httpbrowser=~ m{chromeframe/(\d+\.\d+)\.}) {
+        $clientinfo = 'chromeframe-'.$1;
+    }
     return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-            $clientunicode,$clientos,);
+            $clientunicode,$clientos,$clientmobile,$clientinfo);
 }
 
 ###############################################################
@@ -3060,6 +3086,8 @@ sub get_related_words {
 
 =pod
 
+=back
+
 =head1 Spell checking
 
 =over 4
@@ -3093,12 +3121,6 @@ Note: This sub assumes that aspell is in
 =cut
 
 
-=pod
-
-=back
-
-=cut
-
 sub check_spelling {
     my ($wordlist, $language) = @_;
     my @misspellings;
@@ -3314,7 +3336,7 @@ sub screenname {
 # ------------------------------------------------------------- Confirm Wrapper
 =pod
 
-=item confirmwrapper
+=item * &confirmwrapper($message)
 
 Wrap messages about completion of operation in box
 
@@ -3873,7 +3895,7 @@ sub get_previous_attempt {
                       if ($key =~/$regexp$/ && (defined &$gradesub)) {
                           $value = &$gradesub($value);
                       }
-                      $prevattempts.='<td>'.$value.'&nbsp;</td>';
+                      $prevattempts.='<td>'. $value.'&nbsp;</td>';
                   } else {
                       $prevattempts.='<td>&nbsp;</td>';
                   }
@@ -3889,7 +3911,7 @@ sub get_previous_attempt {
 	      if ($key =~/$regexp$/ && (defined &$gradesub)) {
                   $value = &$gradesub($value);
               }
-	      $prevattempts.='<td>'.$value.'&nbsp;</td>';
+	     $prevattempts.='<td>'.$value.'&nbsp;</td>';
           }
       }
       $prevattempts.= &end_data_table_row().&end_data_table();
@@ -3910,11 +3932,13 @@ sub get_previous_attempt {
 sub format_previous_attempt_value {
     my ($key,$value) = @_;
     if (($key =~ /timestamp/) || ($key=~/duedate/)) {
-	$value = &Apache::lonlocal::locallocaltime($value);
+        $value = &Apache::lonlocal::locallocaltime($value);
     } elsif (ref($value) eq 'ARRAY') {
-	$value = '('.join(', ', @{ $value }).')';
+        $value = &HTML::Entities::encode('('.join(', ', @{ $value }).')','"<>&');
     } elsif ($key =~ /answerstring$/) {
         my %answers = &Apache::lonnet::str2hash($value);
+        my @answer = %answers;
+        %answers = map {&HTML::Entities::encode($_, '"<>&')} @answer;
         my @anskeys = sort(keys(%answers));
         if (@anskeys == 1) {
             my $answer = $answers{$anskeys[0]};
@@ -3937,7 +3961,7 @@ sub format_previous_attempt_value {
             } 
         }
     } else {
-	$value = &unescape($value);
+        $value = &HTML::Entities::encode(&unescape($value), '"<>&');
     }
     return $value;
 }
@@ -4932,7 +4956,7 @@ sub designparm {
 
 Inputs: $url (usually will be undef).
 
-Returns: Path to Construction Space containing the resource or 
+Returns: Path to Authoring Space containing the resource or 
          directory being viewed (or for which action is being taken). 
          If $url is provided, and begins /priv/<domain>/<uname>
          the path will be that portion of the $context argument.
@@ -4995,7 +5019,7 @@ Input: (optional) filename from which br
        is appropriate for use in building the breadcrumb trail.
 
 Returns: HTML div with CSTR path and recent box
-         To be included on Construction Space pages
+         To be included on Authoring Space pages
 
 =cut
 
@@ -5026,7 +5050,7 @@ sub CSTR_pageheader {
     my $output =
          '<div>'
         .&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it?
-        .'<b>'.&mt('Construction Space:').'</b> '
+        .'<b>'.&mt('Authoring Space:').'</b> '
         .'<form name="dirs" method="post" action="'.$formaction
         .'" target="_top">' #FIXME lonpubdir: target="_parent"
         .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,'_top','/priv/'.$udom,undef,undef);
@@ -5114,6 +5138,7 @@ sub bodytag {
         $public = 1;
     }
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
+    my $httphost = $args->{'use_absolute'};
 
     $function = &get_users_function() if (!$function);
     my $img =    &designparm($function.'.img',$domain);
@@ -5129,7 +5154,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);
@@ -5155,16 +5183,14 @@ sub bodytag {
     my $bodytag = "<body $extra_body_attr>".
 	&Apache::lontexconvert::init_math_support($args->{'inherit_jsmath'});
 
-    if ($bodyonly) {
+    &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']);
+
+    if (($bodyonly) || ($no_nav_bar) || ($env{'form.inhibitmenu'} eq 'yes')) {
         return $bodytag;
-    } 
+    }
 
-    my $name = &plainname($env{'user.name'},$env{'user.domain'});
     if ($public) {
 	undef($role);
-    } else {
-	$name = &aboutmewrapper($name,$env{'user.name'},$env{'user.domain'},
-                                undef,'LC_menubuttons_link');
     }
     
     my $titleinfo = '<h1>'.$title.'</h1>';
@@ -5180,11 +5206,6 @@ sub bodytag {
     }
 
     $role = '<span class="LC_nobreak">('.$role.')</span>' if $role;
-    &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']);
-
-        if ($no_nav_bar || $env{'form.inhibitmenu'} eq 'yes') { 
-            return $bodytag; 
-        } 
 
         if ($env{'request.state'} eq 'construct') { $forcereg=1; }
 
@@ -5192,34 +5213,38 @@ sub bodytag {
         #        $titleinfo = &CSTR_pageheader(); #FIXME: Will be removed once all scripts have their own calls
         #    }
 
+        $bodytag .= Apache::lonhtmlcommon::scripttag(
+            Apache::lonmenu::utilityfunctions($httphost), 'start');
 
+        my ($left,$right) = Apache::lonmenu::primary_menu();
 
         if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) {
              if ($dc_info) {
                  $dc_info = qq|<span class="LC_cusr_subheading">$dc_info</span>|;
              }
-             $bodytag .= qq|<div id="LC_nav_bar">$name $role<br />
+             $bodytag .= qq|<div id="LC_nav_bar">$left $role<br />
                 <em>$realm</em> $dc_info</div>|;
             return $bodytag;
         }
 
         unless ($env{'request.symb'} =~ m/\.page___\d+___/) {
-            $bodytag .= qq|<div id="LC_nav_bar">$name $role</div>|;
+            $bodytag .= qq|<div id="LC_nav_bar">$left $role</div>|;
         }
 
-        $bodytag .= Apache::lonhtmlcommon::scripttag(
-            Apache::lonmenu::utilityfunctions(), 'start');
-
-        $bodytag .= Apache::lonmenu::primary_menu();
+        $bodytag .= $right;
 
         if ($dc_info) {
             $dc_info = &dc_courseid_toggle($dc_info);
         }
         $bodytag .= qq|<div id="LC_realm">$realm $dc_info</div>|;
 
+        #if directed to not display the secondary menu, don't.  
+        if ($args->{'no_secondary_menu'}) {
+            return $bodytag;
+        }
         #don't show menus for public users
         if (!$public){
-            $bodytag .= Apache::lonmenu::secondary_menu();
+            $bodytag .= Apache::lonmenu::secondary_menu($httphost);
             $bodytag .= Apache::lonmenu::serverform();
             $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end');
             if ($env{'request.state'} eq 'construct') {
@@ -5279,7 +5304,7 @@ sub make_attr_string {
     }
 
     my $attr_string;
-    foreach my $attr (keys(%$attr_ref)) {
+    foreach my $attr (sort(keys(%$attr_ref))) {
 	$attr_string .= " $attr=\"".$attr_ref->{$attr}.'" ';
     }
     return $attr_string;
@@ -5410,6 +5435,14 @@ form, .inline {
   vertical-align:middle;
 }
 
+.LC_floatleft {
+  float: left;
+}
+
+.LC_floatright {
+  float: right;
+}
+
 .LC_400Box {
   width:400px;
 }
@@ -6485,6 +6518,14 @@ div.LC_edit_problem_saves {
   white-space: nowrap;
 }
 
+.LC_edit_problem_latexhelper{
+    text-align: right;
+}
+
+#LC_edit_problem_colorful div{
+    margin-left: 40px;
+}
+
 img.stift {
   border-width: 0;
   vertical-align: middle;
@@ -6499,6 +6540,7 @@ div.LC_createcourse {
 }
 
 .LC_dccid {
+  float: right;
   margin: 0.2em 0 0 0;
   padding: 0;
   font-size: 90%;
@@ -6596,7 +6638,6 @@ fieldset > legend {
 }
 
 ol.LC_primary_menu {
-  float: right;
   margin: 0;
   padding: 0;
   background-color: $pgbg_or_bgcolor;
@@ -7250,6 +7291,7 @@ sub headtag {
     my $function = $args->{'function'} || &get_users_function();
     my $domain   = $args->{'domain'}   || &determinedomain();
     my $bgcolor  = $args->{'bgcolor'}  || &designparm($function.'.pgbg',$domain);
+    my $httphost = $args->{'use_absolute'};
     my $url = join(':',$env{'user.name'},$env{'user.domain'},
 		   $Apache::lonnet::perlvar{'lonVersion'},
 		   #time(),
@@ -7260,7 +7302,7 @@ sub headtag {
 
     my $result =
 	'<head>'.
-	&font_settings();
+	&font_settings($args);
 
     my $inhibitprint = &print_suppression();
 
@@ -7273,7 +7315,7 @@ sub headtag {
     if (!$args->{'no_nav_bar'} 
 	&& !$args->{'only_body'}
 	&& !$args->{'frameset'}) {
-	$result .= &help_menu_js();
+	$result .= &help_menu_js($httphost);
         $result.=&modal_window();
         $result.=&togglebox_script();
         $result.=&wishlist_window();
@@ -7308,9 +7350,18 @@ ADDMETA
     }
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
     $result .= '<title> LON-CAPA '.$title.'</title>'
-	.'<link rel="stylesheet" type="text/css" href="'.$url.'" />'
+	.'<link rel="stylesheet" type="text/css" href="'.$url.'"';
+    if (!$args->{'frameset'}) {
+        $result .= ' /';
+    }
+    $result .= '>' 
         .$inhibitprint
 	.$head_extra;
+    if ($env{'browser.mobile'}) {
+        $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" />';
+    }
     return $result.'</head>';
 }
 
@@ -7320,15 +7371,21 @@ ADDMETA
 
 Returns neccessary <meta> to set the proper encoding
 
-Inputs: none
+Inputs: optional reference to HASH -- $args passed to &headtag()
 
 =cut
 
 sub font_settings {
+    my ($args) = @_;
     my $headerstring='';
-    if (!$env{'browser.mathml'} && $env{'browser.unicode'}) {
-	$headerstring.=
-	    '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
+    if ((!$env{'browser.mathml'} && $env{'browser.unicode'}) ||
+        ((ref($args) eq 'HASH') && ($args->{'browser.unicode'}))) {
+        $headerstring.=
+            '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"';
+        if (!$args->{'frameset'}) {
+	    $headerstring.= ' /';
+        }
+	$headerstring .= '>'."\n";
     }
     return $headerstring;
 }
@@ -7420,6 +7477,7 @@ Inputs: none
 =cut
 
 sub xml_begin {
+    my ($is_frameset) = @_;
     my $output='';
 
     if ($env{'browser.mathml'}) {
@@ -7431,9 +7489,12 @@ sub xml_begin {
 	    .'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1 plus MathML 2.0 plus SVG 1.1//EN" "http://www.w3.org/2002/04/xhtml-math-svg/xhtml-math-svg.dtd">'
             .'<html xmlns:math="http://www.w3.org/1998/Math/MathML" ' 
 	    .'xmlns="http://www.w3.org/1999/xhtml">';
+    } elsif ($is_frameset) {
+        $output='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'."\n".
+                '<html>'."\n";
     } else {
-	$output='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
-           .'<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">';
+	$output='<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'."\n".
+                '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">'."\n";
     }
     return $output;
 }
@@ -7500,7 +7561,7 @@ sub start_page {
     my ($result,@advtools);
 
     if (! exists($args->{'skip_phases'}{'head'}) ) {
-        $result .= &xml_begin() . &headtag($title, $head_extra, $args);
+        $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args);
     }
     
     if (! exists($args->{'skip_phases'}{'body'}) ) {
@@ -7601,9 +7662,11 @@ function set_wishlistlink(title, path) {
         title = document.title;
         title = title.replace(/^LON-CAPA /,'');
     }
+    title = encodeURIComponent(title);
     if (!path) {
         path = location.pathname;
     }
+    path = encodeURIComponent(path);
     Win = window.open('/adm/wishlist?mode=newLink&setTitle='+title+'&setPath='+path,
                       'wishlistNewLink','width=560,height=350,scrollbars=0');
 }
@@ -7644,12 +7707,12 @@ var modalWindow = {
 		$(".LCmodal-overlay").click(function(){modalWindow.close();});
 	}
 };
-	var openMyModal = function(source,width,height,scrolling)
+	var openMyModal = function(source,width,height,scrolling,transparency,style)
 	{
 		modalWindow.windowId = "myModal";
 		modalWindow.width = width;
 		modalWindow.height = height;
-		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='true' src='" + source + "'>&lt/iframe>";
+		modalWindow.content = "<iframe width='"+width+"' height='"+height+"' frameborder='0' scrolling='"+scrolling+"' allowtransparency='"+transparency+"' src='" + source + "' style='"+style+"'>&lt/iframe>";
 		modalWindow.open();
 	};	
 // END LON-CAPA Internal -->
@@ -7659,16 +7722,18 @@ ENDMODAL
 }
 
 sub modal_link {
-    my ($link,$linktext,$width,$height,$target,$scrolling,$title)=@_;
+    my ($link,$linktext,$width,$height,$target,$scrolling,$title,$transparency,$style)=@_;
     unless ($width) { $width=480; }
     unless ($height) { $height=400; }
     unless ($scrolling) { $scrolling='yes'; }
+    unless ($transparency) { $transparency='true'; }
+
     my $target_attr;
     if (defined($target)) {
         $target_attr = 'target="'.$target.'"';
     }
     return <<"ENDLINK";
-<a href="$link" $target_attr title="$title" onclick="javascript:openMyModal('$link',$width,$height,'$scrolling'); return false;">
+<a href="$link" $target_attr title="$title" onclick="javascript:openMyModal('$link',$width,$height,'$scrolling','$transparency','$style'); return false;">
            $linktext</a>
 ENDLINK
 }
@@ -7695,11 +7760,11 @@ sub modal_adhoc_inner {
     my ($funcname,$width,$height,$content)=@_;
     my $innerwidth=$width-20;
     $content=&js_ready(
-               &start_page('Dialog',undef,{'only_body'=>1,'bgcolor'=>'#FFFFFF'}).
-                 &start_scrollbox($width.'px',$innerwidth.'px',$height.'px').
-                    $content.
+                 &start_page('Dialog',undef,{'only_body'=>1,'bgcolor'=>'#FFFFFF'}).
+                 &start_scrollbox($width.'px',$innerwidth.'px',$height.'px','myModal','#FFFFFF',undef,1).
+                 $content.
                  &end_scrollbox().
-               &end_page()
+                 &end_page()
              );
     return &modal_adhoc_script($funcname,$width,$height,$content);
 }
@@ -7818,11 +7883,9 @@ sub LCprogressbar {
     $LCcurrentid=$$.'_'.$LCidcnt;
     my $starting=&mt('Starting');
     my $content=(<<ENDPROGBAR);
-<p>
   <div id="progressbar$LCcurrentid">
     <span class="pblabel">$starting</span>
   </div>
-</p>
 ENDPROGBAR
     &r_print($r,$content.&LCprogressbar_script($LCcurrentid));
 }
@@ -7918,20 +7981,27 @@ sub validate_page {
 
 
 sub start_scrollbox {
-    my ($outerwidth,$width,$height,$id,$bgcolor)=@_;
+    my ($outerwidth,$width,$height,$id,$bgcolor,$cursor,$needjsready) = @_;
     unless ($outerwidth) { $outerwidth='520px'; }
     unless ($width) { $width='500px'; }
     unless ($height) { $height='200px'; }
     my ($table_id,$div_id,$tdcol);
     if ($id ne '') {
-        $table_id = " id='table_$id'";
-        $div_id = " id='div_$id'";
+        $table_id = ' id="table_'.$id.'"';
+        $div_id = ' id="div_'.$id.'"';
     }
     if ($bgcolor ne '') {
         $tdcol = "background-color: $bgcolor;";
     }
+    my $nicescroll_js;
+    if ($env{'browser.mobile'}) {
+        $nicescroll_js = &nicescroll_javascript('div_'.$id,$cursor,$needjsready);
+    }
     return <<"END";
-<table style="width: $outerwidth; border: 1px solid none;"$table_id><tr><td style="width: $width;$tdcol"><div style="overflow:auto; width:$width; height: $height;"$div_id>
+$nicescroll_js
+
+<table style="width: $outerwidth; border: 1px solid none;"$table_id><tr><td style="width: $width;$tdcol">
+<div style="overflow:auto; width:$width; height:$height;"$div_id>
 END
 }
 
@@ -7939,11 +8009,98 @@ sub end_scrollbox {
     return '</div></td></tr></table>';
 }
 
+sub nicescroll_javascript {
+    my ($id,$cursor,$needjsready,$framecheck,$location) = @_;
+    my %options;
+    if (ref($cursor) eq 'HASH') {
+        %options = %{$cursor};
+    }
+    unless ($options{'railalign'} =~ /^left|right$/) {
+        $options{'railalign'} = 'left';
+    }
+    unless ($options{'cursorcolor'} =~ /^\#\w+$/) {
+        my $function  = &get_users_function();
+        $options{'cursorcolor'} = &designparm($function.'.sidebg',$env{'request.role.domain'});
+        unless ($options{'cursorcolor'} =~ /^\#\w+$/) {
+            $options{'cursorcolor'} = '#00F';
+        }
+    }
+    if ($options{'cursoropacity'} =~ /^[\d.]+$/) {
+        unless ($options{'cursoropacity'} >= 0.0 && $options{'cursoropacity'} <=1.0) {
+            $options{'cursoropacity'}='1.0';
+        }
+    } else {
+        $options{'cursoropacity'}='1.0';
+    }
+    if ($options{'cursorfixedheight'} eq 'none') {
+        delete($options{'cursorfixedheight'});
+    } else {
+        unless ($options{'cursorfixedheight'} =~ /^\d+$/) { $options{'cursorfixedheight'}='50'; }
+    }
+    unless ($options{'railoffset'} =~ /^{[\w\:\d\-,]+}$/) {
+        delete($options{'railoffset'});
+    }
+    my @niceoptions;
+    while (my($key,$value) = each(%options)) {
+        if ($value =~ /^\{.+\}$/) {
+            push(@niceoptions,$key.':'.$value);
+        } else {
+            push(@niceoptions,$key.':"'.$value.'"');
+        }
+    }
+    my $nicescroll_js = '
+$(document).ready(
+      function() {
+          $("#'.$id.'").niceScroll({'.join(',',@niceoptions).'});
+      }
+);
+';
+    if ($framecheck) {
+        $nicescroll_js .= '
+function expand_div(caller) {
+    if (top === self) {
+        document.getElementById("'.$id.'").style.width = "auto";
+        document.getElementById("'.$id.'").style.height = "auto";
+    } else {
+        try {
+            if (parent.frames) {
+                if (parent.frames.length > 1) {
+                    var framesrc = parent.frames[1].location.href;
+                    var currsrc = framesrc.replace(/\#.*$/,"");
+                    if ((caller == "search") || (currsrc == "'.$location.'")) {
+                        document.getElementById("'.$id.'").style.width = "auto";
+                        document.getElementById("'.$id.'").style.height = "auto";
+                    }
+                }
+            }
+        } catch (e) {
+            return;
+        }
+    }
+    return;
+}
+';
+    }
+    if ($needjsready) {
+        $nicescroll_js = '
+<script type="text/javascript">'."\n".$nicescroll_js."\n</script>\n";
+    } else {
+        $nicescroll_js = &Apache::lonhtmlcommon::scripttag($nicescroll_js);
+    }
+    return $nicescroll_js;
+}
+
 sub simple_error_page {
-    my ($r,$title,$msg) = @_;
+    my ($r,$title,$msg,$args) = @_;
+    if (ref($args) eq 'HASH') {
+        if (!$args->{'no_auto_mt_msg'}) { $msg = &mt($msg); }
+    } else {
+        $msg = &mt($msg);
+    }
+
     my $page =
 	&Apache::loncommon::start_page($title).
-	'<p class="LC_error">'.&mt($msg).'</p>'.
+	'<p class="LC_error">'.$msg.'</p>'.
 	&Apache::loncommon::end_page();
     if (ref($r)) {
 	$r->print($page);
@@ -8562,14 +8719,19 @@ sub get_user_info {
 
 =item * &get_user_quota()
 
-Retrieves quota assigned for storage of portfolio files for a user  
+Retrieves quota assigned for storage of user files.
+Default is to report quota for portfolio files.
 
 Incoming parameters:
 1. user's username
 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
 
 Returns:
-1. Disk quota (in Mb) assigned to student.
+1. Disk quota (in MB) assigned to student.
 2. (Optional) Type of setting: custom or default
    (individually assigned or default for user's 
    institutional status).
@@ -8580,7 +8742,7 @@ Returns:
 
 If a value has been stored in the user's environment, 
 it will return that, otherwise it returns the maximal default
-defined for the user's instituional status(es) in the domain.
+defined for the user's institutional status(es) in the domain.
 
 =cut
 
@@ -8588,7 +8750,7 @@ defined for the user's instituional stat
 
 
 sub get_user_quota {
-    my ($uname,$udom) = @_;
+    my ($uname,$udom,$quotaname,$crstype) = @_;
     my ($quota,$quotatype,$settingstatus,$defquota);
     if (!defined($udom)) {
         $udom = $env{'user.domain'};
@@ -8603,27 +8765,58 @@ sub get_user_quota {
         $defquota = 0; 
     } else {
         my $inststatus;
-        if ($udom eq $env{'user.domain'} && $uname eq $env{'user.name'}) {
-            $quota = $env{'environment.portfolioquota'};
-            $inststatus = $env{'environment.inststatus'};
-        } else {
-            my %userenv = 
-                &Apache::lonnet::get('environment',['portfolioquota',
-                                     'inststatus'],$udom,$uname);
-            my ($tmp) = keys(%userenv);
-            if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
-                $quota = $userenv{'portfolioquota'};
-                $inststatus = $userenv{'inststatus'};
-            } else {
-                undef(%userenv);
-            }
-        }
-        ($defquota,$settingstatus) = &default_quota($udom,$inststatus);
-        if ($quota eq '') {
-            $quota = $defquota;
-            $quotatype = 'default';
+        if ($quotaname eq 'course') {
+            if (($env{'course.'.$udom.'_'.$uname.'.num'} eq $uname) &&
+                ($env{'course.'.$udom.'_'.$uname.'.domain'} eq $udom)) {
+                $quota = $env{'course.'.$udom.'_'.$uname.'.internal.uploadquota'};
+            } else {
+                my %cenv = &Apache::lonnet::coursedescription("$udom/$uname");
+                $quota = $cenv{'internal.uploadquota'};
+            }
         } else {
-            $quotatype = 'custom';
+            if ($udom eq $env{'user.domain'} && $uname eq $env{'user.name'}) {
+                if ($quotaname eq 'author') {
+                    $quota = $env{'environment.authorquota'};
+                } else {
+                    $quota = $env{'environment.portfolioquota'};
+                }
+                $inststatus = $env{'environment.inststatus'};
+            } else {
+                my %userenv = 
+                    &Apache::lonnet::get('environment',['portfolioquota',
+                                         'authorquota','inststatus'],$udom,$uname);
+                my ($tmp) = keys(%userenv);
+                if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
+                    if ($quotaname eq 'author') {
+                        $quota = $userenv{'authorquota'};
+                    } else {
+                        $quota = $userenv{'portfolioquota'};
+                    }
+                    $inststatus = $userenv{'inststatus'};
+                } else {
+                    undef(%userenv);
+                }
+            }
+        }
+        if ($quota eq '' || wantarray) {
+            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')) { 
+                    $defquota = $domdefs{$crstype.'quota'};
+                }
+                if ($defquota eq '') {
+                    $defquota = 500;
+                }
+            } else {
+                ($defquota,$settingstatus) = &default_quota($udom,$inststatus,$quotaname);
+            }
+            if ($quota eq '') {
+                $quota = $defquota;
+                $quotatype = 'default';
+            } else {
+                $quotatype = 'custom';
+            }
         }
     }
     if (wantarray) {
@@ -8643,54 +8836,60 @@ Retrieves default quota assigned for sto
 given an (optional) user's institutional status.
 
 Incoming parameters:
+
 1. domain
 2. (Optional) institutional status(es).  This is a : separated list of 
    status types (e.g., faculty, staff, student etc.)
    which apply to the user for whom the default is being retrieved.
    If the institutional status string in undefined, the domain
-   default quota will be returned. 
+   default quota will be returned.
+3.  quota name - portfolio, author, or course
+   (if no quota name provided, defaults to portfolio).
 
 Returns:
-1. Default disk quota (in Mb) for user portfolios in the domain.
+
+1. Default disk quota (in MB) for user portfolios in the domain.
 2. (Optional) institutional type which determined the value of the
    default quota.
 
 If a value has been stored in the domain's configuration db,
 it will return that, otherwise it returns 20 (for backwards 
 compatibility with domains which have not set up a configuration
-db file; the original statically defined portfolio quota was 20 Mb). 
+db file; the original statically defined portfolio quota was 20 MB). 
 
 If the user's status includes multiple types (e.g., staff and student),
 the largest default quota which applies to the user determines the
 default quota returned.
 
-=back
-
 =cut
 
 ###############################################
 
 
 sub default_quota {
-    my ($udom,$inststatus) = @_;
+    my ($udom,$inststatus,$quotaname) = @_;
     my ($defquota,$settingstatus);
     my %quotahash = &Apache::lonnet::get_dom('configuration',
                                             ['quotas'],$udom);
+    my $key = 'defaultquota';
+    if ($quotaname eq 'author') {
+        $key = 'authorquota';
+    }
     if (ref($quotahash{'quotas'}) eq 'HASH') {
         if ($inststatus ne '') {
             my @statuses = map { &unescape($_); } split(/:/,$inststatus);
             foreach my $item (@statuses) {
-                if (ref($quotahash{'quotas'}{'defaultquota'}) eq 'HASH') {
-                    if ($quotahash{'quotas'}{'defaultquota'}{$item} ne '') {
+                if (ref($quotahash{'quotas'}{$key}) eq 'HASH') {
+                    if ($quotahash{'quotas'}{$key}{$item} ne '') {
                         if ($defquota eq '') {
-                            $defquota = $quotahash{'quotas'}{'defaultquota'}{$item};
+                            $defquota = $quotahash{'quotas'}{$key}{$item};
                             $settingstatus = $item;
-                        } elsif ($quotahash{'quotas'}{'defaultquota'}{$item} > $defquota) {
-                            $defquota = $quotahash{'quotas'}{'defaultquota'}{$item};
+                        } elsif ($quotahash{'quotas'}{$key}{$item} > $defquota) {
+                            $defquota = $quotahash{'quotas'}{$key}{$item};
                             $settingstatus = $item;
                         }
                     }
-                } else {
+                } elsif ($key eq 'defaultquota') {
                     if ($quotahash{'quotas'}{$item} ne '') {
                         if ($defquota eq '') {
                             $defquota = $quotahash{'quotas'}{$item};
@@ -8704,16 +8903,25 @@ sub default_quota {
             }
         }
         if ($defquota eq '') {
-            if (ref($quotahash{'quotas'}{'defaultquota'}) eq 'HASH') {
-                $defquota = $quotahash{'quotas'}{'defaultquota'}{'default'};
-            } else {
+            if (ref($quotahash{'quotas'}{$key}) eq 'HASH') {
+                $defquota = $quotahash{'quotas'}{$key}{'default'};
+            } elsif ($key eq 'defaultquota') {
                 $defquota = $quotahash{'quotas'}{'default'};
             }
             $settingstatus = 'default';
+            if ($defquota eq '') {
+                if ($quotaname eq 'author') {
+                    $defquota = 500;
+                }
+            }
         }
     } else {
         $settingstatus = 'default';
-        $defquota = 20;
+        if ($quotaname eq 'author') {
+            $defquota = 500;
+        } else {
+            $defquota = 20;
+        }
     }
     if (wantarray) {
         return ($defquota,$settingstatus);
@@ -8722,6 +8930,64 @@ sub default_quota {
     }
 }
 
+###############################################
+
+=pod
+
+=item * &excess_filesize_warning()
+
+Returns warning message if upload of file to authoring space, or copying
+of existing file within authoring space will cause quota for the authoring
+space to be exceeded.
+
+Same, if upload of a file directly to a course/community via Course Editor
+will cause quota for uploaded content for the course to be exceeded.
+
+Inputs: 7 
+1. username or coursenum
+2. domain
+3. context ('author' or 'course')
+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).
+
+Returns: 1 scalar: HTML to display containing warning if quota would be exceeded,
+         otherwise return null.
+
+=back
+
+=cut
+
+sub excess_filesize_warning {
+    my ($uname,$udom,$context,$filename,$filesize,$action,$quotatype) = @_;
+    my $current_disk_usage = 0;
+    my $disk_quota = &get_user_quota($uname,$udom,$context,$quotatype); #expressed in MB
+    if ($context eq 'author') {
+        my $authorspace = $Apache::lonnet::perlvar{'lonDocRoot'}."/priv/$udom/$uname";
+        $current_disk_usage = &Apache::lonnet::diskusage($udom,$uname,$authorspace);
+    } else {
+        foreach my $subdir ('docs','supplemental') {
+            $current_disk_usage += &Apache::lonnet::diskusage($udom,$uname,"userfiles/$subdir",1);
+        }
+    }
+    $disk_quota = int($disk_quota * 1000);
+    if (($current_disk_usage + $filesize) > $disk_quota) {
+        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).'</p>'.
+               '<p>'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+                            $disk_quota,$current_disk_usage).
+               '</p>';
+    }
+    return;
+}
+
+###############################################
+
+
+
+
 sub get_secgrprole_info {
     my ($cdom,$cnum,$needroles,$type)  = @_;
     my %sections_count = &get_sections($cdom,$cnum);
@@ -9163,7 +9429,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');
@@ -9561,11 +9834,10 @@ sub ask_for_embedded_content {
     my $numexisting = 0;
     my $numunused = 0;
     my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath,$cdom,$cnum,
-        $fileloc,$filename,$delete_output,$modify_output,$title,$symb,$path);
+        $fileloc,$filename,$delete_output,$modify_output,$title,$symb,$path,$navmap);
     my $heading = &mt('Upload embedded files');
     my $buttontext = &mt('Upload');
 
-    my ($navmap,$cdom,$cnum);
     if ($env{'request.course.id'}) {
         if ($actionurl eq '/adm/dependencies') {
             $navmap = Apache::lonnavmaps::navmap->new();
@@ -9649,17 +9921,18 @@ sub ask_for_embedded_content {
         } else {
             $embed_file = $file;
         }
-        my $absolutepath;
+        my ($absolutepath,$cleaned_file);
         if ($embed_file =~ m{^\w+://}) {
-            $newfiles{$embed_file} = 1;
-            $mapping{$embed_file} = $embed_file;
+            $cleaned_file = $embed_file;
+            $newfiles{$cleaned_file} = 1;
+            $mapping{$cleaned_file} = $embed_file;
         } else {
+            $cleaned_file = &clean_path($embed_file);
             if ($embed_file =~ m{^/}) {
                 $absolutepath = $embed_file;
-                $embed_file =~ s{^(/+)}{};
             }
-            if ($embed_file =~ m{/}) {
-                my ($path,$fname) = ($embed_file =~ m{^(.+)/([^/]*)$});
+            if ($cleaned_file =~ m{/}) {
+                my ($path,$fname) = ($cleaned_file =~ m{^(.+)/([^/]*)$});
                 $path = &check_for_traversal($path,$url,$toplevel);
                 my $item = $fname;
                 if ($path ne '') {
@@ -9676,9 +9949,9 @@ sub ask_for_embedded_content {
             } else {
                 $dependencies{$embed_file} = 1;
                 if ($absolutepath) {
-                    $mapping{$embed_file} = $absolutepath;
+                    $mapping{$cleaned_file} = $absolutepath;
                 } else {
-                    $mapping{$embed_file} = $embed_file;
+                    $mapping{$cleaned_file} = $embed_file;
                 }
             }
         }
@@ -10052,6 +10325,46 @@ sub ask_for_embedded_content {
     return ($output,$counter,$numpathchg);
 }
 
+=pod
+
+=item * clean_path($name)
+
+Performs clean-up of directories, subdirectories and filename in an
+embedded object, referenced in an HTML file which is being uploaded
+to a course or portfolio, where 
+"Upload embedded images/multimedia files if HTML file" checkbox was
+checked.
+
+Clean-up is similar to replacements in lonnet::clean_filename()
+except each / between sub-directory and next level is preserved.
+
+=cut
+
+sub clean_path {
+    my ($embed_file) = @_;
+    $embed_file =~s{^/+}{};
+    my @contents;
+    if ($embed_file =~ m{/}) {
+        @contents = split(/\//,$embed_file);
+    } else {
+        @contents = ($embed_file);
+    }
+    my $lastidx = scalar(@contents)-1;
+    for (my $i=0; $i<=$lastidx; $i++) { 
+        $contents[$i]=~s{\\}{/}g;
+        $contents[$i]=~s/\s+/\_/g;
+        $contents[$i]=~s{[^/\w\.\-]}{}g;
+        if ($i == $lastidx) {
+            $contents[$i]=~s/\.(\d+)(?=\.)/_$1/g;
+        }
+    }
+    if ($lastidx > 0) {
+        return join('/',@contents);
+    } else {
+        return $contents[0];
+    }
+}
+
 sub embedded_file_element {
     my ($context,$num,$embed_file,$mapping,$allfiles,$codebase,$type) = @_;
     return unless ((ref($mapping) eq 'HASH') && (ref($allfiles) eq 'HASH') &&
@@ -10176,7 +10489,8 @@ sub upload_embedded {
         # Check if extension is valid
         if (($fname =~ /\.(\w+)$/) &&
             (&Apache::loncommon::fileembstyle($1) eq 'hdn')) {
-            $output .= &mt('Invalid file extension ([_1]) - reserved for LONCAPA use - rename the file with a different extension and re-upload. ',$1).'<br />';
+            $output .= &mt('Invalid file extension ([_1]) - reserved for internal use.',$1)
+                      .' '.&mt('Rename the file with a different extension and re-upload.').'<br />';
             next;
         } elsif (($fname =~ /\.(\w+)$/) &&
                  (!defined(&Apache::loncommon::fileembstyle($1)))) {
@@ -10440,6 +10754,7 @@ sub modify_html_refs {
                         my $numchg = ($content =~ s{($attrib_regexp\s*=\s*['"]?)\Q$ref\E(['"]?)}{$1$newname$2}gi);
                         $count += $numchg;
                         $allfiles{$newname} = $allfiles{$ref};
+                        delete($allfiles{$ref});
                     }
                     if ($env{'form.embedded_codebase_'.$i} ne '') {
                         $codebase = &unescape($env{'form.embedded_codebase_'.$i});
@@ -10615,11 +10930,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);
                         }
                     }
@@ -10628,21 +10943,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);
         }
     }
@@ -10733,16 +11048,43 @@ sub decompress_form {
         }
     }
     if ($mimetype =~ m{^application/(x\-)?(compressed|zip)}) {
-        my @camtasia = ("$topdir/","$topdir/index.html",
+        my @camtasia6 = ("$topdir/","$topdir/index.html",
                         "$topdir/media/",
                         "$topdir/media/$topdir.mp4",
                         "$topdir/media/FirstFrame.png",
                         "$topdir/media/player.swf",
                         "$topdir/media/swfobject.js",
                         "$topdir/media/expressInstall.swf");
-        my @diffs = &compare_arrays(\@paths,\@camtasia);
+        my @camtasia8 = ("$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/handlebars.js",
+                         "$topdir/scripts/jquery-1.7.1.min.js",
+                         "$topdir/scripts/jquery-ui-1.8.15.custom.min.js",
+                         "$topdir/scripts/modernizr.js",
+                         "$topdir/scripts/player-min.js",
+                         "$topdir/scripts/swfobject.js",
+                         "$topdir/skins/",
+                         "$topdir/skins/configuration_express.xml",
+                         "$topdir/skins/express_show/",
+                         "$topdir/skins/express_show/player-min.css",
+                         "$topdir/skins/express_show/spritesheet.png");
+        my @diffs = &compare_arrays(\@paths,\@camtasia6);
         if (@diffs == 0) {
-            $is_camtasia = 1;
+            $is_camtasia = 6;
+        } else {
+            @diffs = &compare_arrays(\@paths,\@camtasia8);
+            if (@diffs == 0) {
+                $is_camtasia = 8;
+            }
         }
     }
     my $output;
@@ -10754,7 +11096,7 @@ sub decompress_form {
 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 == 1) {
+            if (document.uploaded_decompress.autoextract_camtasia[i].value == $is_camtasia) {
 
                 document.getElementById('camtasia_titles').style.display='block';
             } else {
@@ -10817,7 +11159,7 @@ ENDCAM
     if ($is_camtasia) {
         $output .= $lt{'auto'}.'<br />'.
                    '<span class="LC_nobreak">'.$lt{'proa'}.'<label>'.
-                   '<input type="radio" name="autoextract_camtasia" value="1" onclick="javascript:camtasiaToggle();" checked="checked" />'.
+                   '<input type="radio" name="autoextract_camtasia" value="'.$is_camtasia.'" onclick="javascript:camtasiaToggle();" checked="checked" />'.
                    $lt{'yes'}.'</label>&nbsp;<label>'.
                    '<input type="radio" name="autoextract_camtasia" value="0" onclick="javascript:camtasiaToggle();" />'.
                    $lt{'no'}.'</label></span><br />'.
@@ -10940,7 +11282,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');
@@ -11050,6 +11392,7 @@ sub process_decompression {
                                                            \%titles,\%children);
                         }
                         if ($env{'form.autoextract_camtasia'}) {
+                            my $version = $env{'form.autoextract_camtasia'};
                             my %displayed;
                             my $total = 1;
                             $env{'form.archive_directory'} = [];
@@ -11068,12 +11411,15 @@ sub process_decompression {
                                     $env{'form.archive_'.$i} = 'display';
                                     $env{'form.archive_title_'.$i} = $env{'form.camtasia_foldername'};
                                     $displayed{'folder'} = $i;
-                                } elsif ($item eq "$contents[0]/index.html") {
+                                } elsif ((($item eq "$contents[0]/index.html") && ($version == 6)) ||
+                                         (($item eq "$contents[0]/$contents[0]".'.html') && ($version == 8))) { 
                                     $env{'form.archive_'.$i} = 'display';
                                     $env{'form.archive_title_'.$i} = $env{'form.camtasia_moviename'};
                                     $displayed{'web'} = $i;
                                 } else {
-                                    if ($item eq "$contents[0]/media") {
+                                    if ((($item eq "$contents[0]/media") && ($version == 6)) ||
+                                        ((($item eq "$contents[0]/scripts") || ($item eq "$contents[0]/skins") ||
+                                             ($item eq "$contents[0]/skins/express_show")) && ($version == 8))) {
                                         push(@{$env{'form.archive_directory'}},$i);
                                     }
                                     $env{'form.archive_'.$i} = 'dependency';
@@ -11800,7 +12146,7 @@ sub cleanup_empty_dirs {
 
 =pod
 
-=item &get_folder_hierarchy()
+=item * &get_folder_hierarchy()
 
 Provides hierarchy of names of folders/sub-folders containing the current
 item,
@@ -11828,7 +12174,7 @@ sub get_folder_hierarchy {
                 my @pcs = split(/,/,$pcslist);
                 foreach my $pc (@pcs) {
                     if ($pc == 1) {
-                        push(@pathitems,&mt('Main Course Documents'));
+                        push(@pathitems,&mt('Main Content'));
                     } else {
                         my $res = $navmap->getByMapPc($pc);
                         if (ref($res)) {
@@ -11843,7 +12189,7 @@ sub get_folder_hierarchy {
             }
             if ($showitem) {
                 if ($mapres->{ID} eq '0.0') {
-                    push(@pathitems,&mt('Main Course Documents'));
+                    push(@pathitems,&mt('Main Content'));
                 } else {
                     my $maptitle = $mapres->compTitle();
                     $maptitle =~ s/\W+/_/g;
@@ -11910,6 +12256,9 @@ sub get_turnedin_filepath {
                             my $title = $res->compTitle();
                             $title =~ s/\W+/_/g;
                             if ($title ne '') {
+                                if (($pc > 1) && (length($title) > 12)) {
+                                    $title = substr($title,0,12);
+                                }
                                 push(@pathitems,$title);
                             }
                         }
@@ -11918,6 +12267,9 @@ sub get_turnedin_filepath {
                 my $maptitle = $mapres->compTitle();
                 $maptitle =~ s/\W+/_/g;
                 if ($maptitle ne '') {
+                    if (length($maptitle) > 12) {
+                        $maptitle = substr($maptitle,0,12);
+                    }
                     push(@pathitems,$maptitle);
                 }
                 unless ($env{'request.state'} eq 'construct') {
@@ -11958,6 +12310,9 @@ sub get_turnedin_filepath {
                 $restitle = time;
             }
         }
+        if (length($restitle) > 12) {
+            $restitle = substr($restitle,0,12);
+        }
         push(@pathitems,$restitle);
         $path .= join('/',@pathitems);
     }
@@ -12895,16 +13250,20 @@ sub restore_settings {
 
 =item * &build_recipient_list()
 
-Build recipient lists for five types of e-mail:
+Build recipient lists for following types of e-mail:
 (a) Error Reports, (b) Package Updates, (c) lonstatus warnings/errors
-(d) Help requests, (e) Course requests needing approval,  generated by
-lonerrorhandler.pm, CHECKRPMS, loncron, lonsupportreq.pm and
-loncoursequeueadmin.pm respectively.
+(d) Help requests, (e) Course requests needing approval, (f) loncapa
+module change checking, student/employee ID conflict checks, as
+generated by lonerrorhandler.pm, CHECKRPMS, loncron,
+lonsupportreq.pm, loncoursequeueadmin.pm, searchcat.pl respectively.
 
 Inputs:
 defmail (scalar - email address of default recipient), 
-mailing type (scalar - errormail, packagesmail, or helpdeskmail), 
+mailing type (scalar: errormail, packagesmail, helpdeskmail,
+requestsmail, updatesmail, or idconflictsmail).
+
 defdom (domain for which to retrieve configuration settings),
+
 origmail (scalar - email address of recipient from loncapa.conf, 
 i.e., predates configuration by DC via domainprefs.pm 
 
@@ -13100,7 +13459,7 @@ sub extract_categories {
 
 =pod
 
-=item *&recurse_categories()
+=item * &recurse_categories()
 
 Recursively used to generate breadcrumb trails for course categories.
 
@@ -13171,7 +13530,7 @@ sub recurse_categories {
 
 =pod
 
-=item *&assign_categories_table()
+=item * &assign_categories_table()
 
 Create a datatable for display of hierarchical categories in a domain,
 with checkboxes to allow a course to be categorized. 
@@ -13248,7 +13607,7 @@ sub assign_categories_table {
 
 =pod
 
-=item *&assign_category_rows()
+=item * &assign_category_rows()
 
 Create a datatable row for display of nested categories in a domain,
 with checkboxes to allow a course to be categorized,called recursively.
@@ -13282,7 +13641,7 @@ sub assign_category_rows {
             if (ref($cats->[$depth]{$parent}) eq 'ARRAY') {
                 my $numchildren = @{$cats->[$depth]{$parent}};
                 my $css_class = $itemcount%2?' class="LC_odd_row"':'';
-                $text .= '<td><table class="LC_datatable">';
+                $text .= '<td><table class="LC_data_table">';
                 for (my $j=0; $j<$numchildren; $j++) {
                     $name = $cats->[$depth]{$parent}[$j];
                     $item = &escape($name).':'.&escape($parent).':'.$depth;
@@ -13314,6 +13673,12 @@ sub assign_category_rows {
     return $text;
 }
 
+=pod
+
+=back
+
+=cut
+
 ############################################################
 ############################################################
 
@@ -13566,7 +13931,7 @@ sub check_clone {
 }
 
 sub construct_course {
-    my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category) = @_;
+    my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category,$coderef) = @_;
     my $outcome;
     my $linefeed =  '<br />'."\n";
     if ($context eq 'auto') {
@@ -13663,8 +14028,12 @@ sub construct_course {
                    'plc.users.denied',
                    'hidefromcat',
                    'checkforpriv',
-                   'categories'],
+                   'categories',
+                   'internal.uniquecode'],
                    $$crsudom,$$crsunum);
+        if ($args->{'textbook'}) {
+            $cenv{'internal.textbook'} = $args->{'textbook'};
+        }
     }
 
 #
@@ -13848,6 +14217,25 @@ sub construct_course {
 	}
     }
 
+#
+#  generate and store uniquecode (available to course requester), if course should have one.
+#
+    if ($args->{'uniquecode'}) {
+        my ($code,$error) = &make_unique_code($$crsudom,$$crsunum);
+        if ($code) {
+            $cenv{'internal.uniquecode'} = $code;
+            my %crsinfo =
+                &Apache::lonnet::courseiddump($$crsudom,'.',1,'.','.',$$crsunum,undef,undef,'.');
+            if (ref($crsinfo{$$crsudom.'_'.$$crsunum}) eq 'HASH') {
+                $crsinfo{$$crsudom.'_'.$$crsunum}{'uniquecode'} = $code;
+                my $putres = &Apache::lonnet::courseidput($$crsudom,\%crsinfo,$crsuhome,'notime');
+            } 
+            if (ref($coderef)) {
+                $$coderef = $code;
+            }
+        }
+    }
+
     if ($args->{'disresdis'}) {
         $cenv{'pch.roles.denied'}='st';
     }
@@ -13916,6 +14304,60 @@ sub construct_course {
     return (1,$outcome);
 }
 
+sub make_unique_code {
+    my ($cdom,$cnum) = @_;
+    # get lock on uniquecodes db
+    my $lockhash = {
+                      $cnum."\0".'uniquecodes' => $env{'user.name'}.
+                                                  ':'.$env{'user.domain'},
+                   };
+    my $tries = 0;
+    my $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+    my ($code,$error);
+  
+    while (($gotlock ne 'ok') && ($tries<3)) {
+        $tries ++;
+        sleep 1;
+        $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+    }
+    if ($gotlock eq 'ok') {
+        my %currcodes = &Apache::lonnet::dump_dom('uniquecodes',$cdom);
+        my $gotcode;
+        my $attempts = 0;
+        while ((!$gotcode) && ($attempts < 100)) {
+            $code = &generate_code();
+            if (!exists($currcodes{$code})) {
+                $gotcode = 1;
+                unless (&Apache::lonnet::newput_dom('uniquecodes',{ $code => $cnum },$cdom) eq 'ok') {
+                    $error = 'nostore';
+                }
+            }
+            $attempts ++;
+        }
+        my @del_lock = ($cnum."\0".'uniquecodes');
+        my $dellockoutcome = &Apache::lonnet::del_dom('uniquecodes',\@del_lock,$cdom);
+    } else {
+        $error = 'nolock';
+    }
+    return ($code,$error);
+}
+
+sub generate_code {
+    my $code;
+    my @letts = qw(B C D G H J K M N P Q R S T V W X Z);
+    for (my $i=0; $i<6; $i++) {
+        my $lettnum = int (rand 2);
+        my $item = '';
+        if ($lettnum) {
+            $item = $letts[int( rand(18) )];
+        } else {
+            $item = 1+int( rand(8) );
+        }
+        $code .= $item;
+    }
+    return $code;
+}
+
 ############################################################
 ############################################################
 
@@ -13943,11 +14385,12 @@ sub group_term {
 }
 
 sub course_types {
-    my @types = ('official','unofficial','community');
+    my @types = ('official','unofficial','community','textbook');
     my %typename = (
                          official   => 'Official course',
                          unofficial => 'Unofficial course',
                          community  => 'Community',
+                         textbook   => 'Textbook course',
                    );
     return (\@types,\%typename);
 }
@@ -14080,7 +14523,7 @@ sub init_user_environment {
 # ------------------------------------ Check browser type and MathML capability
 
     my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
-        $clientunicode,$clientos) = &decode_user_agent($r);
+        $clientunicode,$clientos,$clientmobile,$clientinfo) = &decode_user_agent($r);
 
 # ------------------------------------------------------------- Get environment
 
@@ -14111,6 +14554,8 @@ sub init_user_environment {
 	     "browser.mathml"     => $clientmathml,
 	     "browser.unicode"    => $clientunicode,
 	     "browser.os"         => $clientos,
+             "browser.mobile"     => $clientmobile,
+             "browser.info"       => $clientinfo,
 	     "server.domain"      => $Apache::lonnet::perlvar{'lonDefDomain'},
 	     "request.course.fn"  => '',
 	     "request.course.uri" => '',
@@ -14130,6 +14575,12 @@ sub init_user_environment {
 	    $env{'browser.interface'}=$form->{'interface'};
 	}
 
+        if ($form->{'iptoken'}) {
+            my $lonhost = $r->dir_config('lonHostID');
+            $initial_env{"user.noloadbalance"} = $lonhost;
+            $env{'user.noloadbalance'} = $lonhost;
+        }
+
         my %is_adv = ( is_adv => $env{'user.adv'} );
         my %domdef;
         unless ($domain eq 'public') {
@@ -14142,7 +14593,7 @@ sub init_user_environment {
                                                   undef,\%userenv,\%domdef,\%is_adv);
         }
 
-        foreach my $crstype ('official','unofficial','community') {
+        foreach my $crstype ('official','unofficial','community','textbook') {
             $userenv{'canrequest.'.$crstype} =
                 &Apache::lonnet::usertools_access($username,$domain,$crstype,
                                                   'reload','requestcourses',
@@ -14247,6 +14698,535 @@ sub clean_symb {
     return ($symb,$enc);
 }
 
+############################################################
+############################################################
+
+=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 {
+                $filter->{$item} =~ s/\W//g;
+            }
+            if (!$filter->{$item}) {
+                $filter->{$item} = '';
+            }
+        }
+        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').' '.$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 ne 'quotacheck') {
+        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";
+        }
+    }
+    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
+
+=cut
+
+
 sub build_release_hashes {
     my ($checkparms,$checkresponsetypes,$checkcrstypes,$anonsurvey,$randomizetry) = @_;
     return unless((ref($checkparms) eq 'HASH') && (ref($checkresponsetypes) eq 'HASH') &&
@@ -14363,6 +15343,30 @@ sub parse_supplemental_title {
     return $title;
 }
 
+sub recurse_supplemental {
+    my ($cnum,$cdom,$suppmap,$numfiles,$errors) = @_;
+    if ($suppmap) {
+        my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap);
+        if ($fatal) {
+            $errors ++;
+        } else {
+            if ($#LONCAPA::map::resources > 0) {
+                foreach my $res (@LONCAPA::map::resources) {
+                    my ($title,$src,$ext,$type,$status)=split(/\:/,$res);
+                    if (($src ne '') && ($status eq 'res')) {
+                        if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
+                            ($numfiles,$errors) = &recurse_supplemental($cnum,$cdom,$1,$numfiles,$errors);
+                        } else {
+                            $numfiles ++;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return ($numfiles,$errors);
+}
+
 sub symb_to_docspath {
     my ($symb) = @_;
     return unless ($symb);
@@ -14392,7 +15396,7 @@ sub symb_to_docspath {
                     my $thistitle = $res->title();
                     $path .= '&'.
                              &Apache::lonhtmlcommon::entity_encode($thisurl).'&'.
-                             &Apache::lonhtmlcommon::entity_encode($thistitle).
+                             &escape($thistitle).
                              ':'.$res->randompick().
                              ':'.$res->randomout().
                              ':'.$res->encrypted().
@@ -14404,11 +15408,11 @@ sub symb_to_docspath {
         $path =~ s/^\&//;
         my $maptitle = $mapresobj->title();
         if ($mapurl eq 'default') {
-            $maptitle = 'Main Course Documents';
+            $maptitle = 'Main Content';
         }
         $path .= (($path ne '')? '&' : '').
                  &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
-                 &Apache::lonhtmlcommon::entity_encode($maptitle).
+                 &escape($maptitle).
                  ':'.$mapresobj->randompick().
                  ':'.$mapresobj->randomout().
                  ':'.$mapresobj->encrypted().
@@ -14418,14 +15422,14 @@ sub symb_to_docspath {
         my $maptitle = &Apache::lonnet::gettitle($mapurl);
         my $ispage = (($type eq 'page')? 1 : '');
         if ($mapurl eq 'default') {
-            $maptitle = 'Main Course Documents';
+            $maptitle = 'Main Content';
         }
         $path = &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
-                &Apache::lonhtmlcommon::entity_encode($maptitle).':::::'.$ispage;
+                &escape($maptitle).':::::'.$ispage;
     }
     unless ($mapurl eq 'default') {
         $path = 'default&'.
-                &Apache::lonhtmlcommon::entity_encode('Main Course Documents').
+                &escape('Main Content').
                 ':::::&'.$path;
     }
     return $path;
@@ -14438,15 +15442,15 @@ sub captcha_display {
     if ($captcha eq 'original') {
         $output = &create_captcha();
         unless ($output) {
-            $error = 'captcha'; 
+            $error = 'captcha';
         }
     } elsif ($captcha eq 'recaptcha') {
         $output = &create_recaptcha($pubkey);
         unless ($output) {
-            $error = 'recaptcha'; 
+            $error = 'recaptcha';
         }
     }
-    return ($output,$error);
+    return ($output,$error,$captcha);
 }
 
 sub captcha_response {
@@ -14522,8 +15526,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" />';
+                      '<input type="text" size="5" name="code" value="" autocomplete="off" />'.
+                      '<br />'.
+                      '<img src="'.$captcha_params{'www_output_dir'}.'/'.$md5sum.'.png" alt="captcha" />';
             last;
         }
     }
@@ -14564,11 +15569,15 @@ 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).
+           $captcha->get_html($pubkey,undef,$use_ssl).
            &mt('If either word is hard to read, [_1] will replace them.',
-               '<image src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
+               '<img src="/res/adm/pages/refresh.gif" alt="reCAPTCHA refresh" />').
            '<br /><br />';
 }
 
@@ -14589,11 +15598,82 @@ sub check_recaptcha {
     return $captcha_chk;
 }
 
-=pod
+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);
+}
 
-=back
+sub cleanup_html {
+    my ($incoming) = @_;
+    my $outgoing;
+    if ($incoming ne '') {
+        $outgoing = $incoming;
+        $outgoing =~ s/;/&#059;/g;
+        $outgoing =~ s/\#/&#035;/g;
+        $outgoing =~ s/\&/&#038;/g;
+        $outgoing =~ s/</&#060;/g;
+        $outgoing =~ s/>/&#062;/g;
+        $outgoing =~ s/\(/&#040/g;
+        $outgoing =~ s/\)/&#041;/g;
+        $outgoing =~ s/"/&#034;/g;
+        $outgoing =~ s/'/&#039;/g;
+        $outgoing =~ s/\$/&#036;/g;
+        $outgoing =~ s{/}{&#047;}g;
+        $outgoing =~ s/=/&#061;/g;
+        $outgoing =~ s/\\/&#092;/g
+    }
+    return $outgoing;
+}
 
-=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__;