--- loncom/interface/loncommon.pm	2013/05/21 18:54:15	1.1129
+++ loncom/interface/loncommon.pm	2013/08/13 16:19:26	1.1145
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1129 2013/05/21 18:54:15 raeburn Exp $
+# $Id: loncommon.pm,v 1.1145 2013/08/13 16:19:26 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -2349,6 +2349,10 @@ Outputs:
 
 =item * $clientos
 
+=item * $clientmobile
+
+=item * $clientinfo
+
 =back
 
 =back 
@@ -2367,6 +2371,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 +2383,7 @@ sub decode_user_agent {
 	}
     }
     my $clientos='unknown';
+    my $clientinfo;
     if (($httpbrowser=~/linux/i) ||
         ($httpbrowser=~/unix/i) ||
         ($httpbrowser=~/ux/i) ||
@@ -2389,8 +2395,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 +3074,8 @@ sub get_related_words {
 
 =pod
 
+=back
+
 =head1 Spell checking
 
 =over 4
@@ -3093,12 +3109,6 @@ Note: This sub assumes that aspell is in
 =cut
 
 
-=pod
-
-=back
-
-=cut
-
 sub check_spelling {
     my ($wordlist, $language) = @_;
     my @misspellings;
@@ -3314,7 +3324,7 @@ sub screenname {
 # ------------------------------------------------------------- Confirm Wrapper
 =pod
 
-=item confirmwrapper
+=item * &confirmwrapper($message)
 
 Wrap messages about completion of operation in box
 
@@ -4932,7 +4942,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 +5005,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 +5036,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);
@@ -5155,16 +5165,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 +5188,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,25 +5195,25 @@ sub bodytag {
         #        $titleinfo = &CSTR_pageheader(); #FIXME: Will be removed once all scripts have their own calls
         #    }
 
+        $bodytag .= Apache::lonhtmlcommon::scripttag(
+            Apache::lonmenu::utilityfunctions(), '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);
@@ -5410,6 +5413,14 @@ form, .inline {
   vertical-align:middle;
 }
 
+.LC_floatleft {
+  float: left;
+}
+
+.LC_floatright {
+  float: right;
+}
+
 .LC_400Box {
   width:400px;
 }
@@ -6499,6 +6510,7 @@ div.LC_createcourse {
 }
 
 .LC_dccid {
+  float: right;
   margin: 0.2em 0 0 0;
   padding: 0;
   font-size: 90%;
@@ -6596,7 +6608,6 @@ fieldset > legend {
 }
 
 ol.LC_primary_menu {
-  float: right;
   margin: 0;
   padding: 0;
   background-color: $pgbg_or_bgcolor;
@@ -7311,6 +7322,11 @@ ADDMETA
 	.'<link rel="stylesheet" type="text/css" href="'.$url.'" />'
         .$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>';
 }
 
@@ -7644,12 +7660,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 +7675,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 +7713,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);
 }
@@ -7916,20 +7934,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
 }
 
@@ -7937,6 +7962,87 @@ 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 $page =
@@ -8560,11 +8666,16 @@ 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 or community, if quota name is
+   course
 
 Returns:
 1. Disk quota (in Mb) assigned to student.
@@ -8578,7 +8689,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
 
@@ -8586,7 +8697,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'};
@@ -8601,27 +8712,57 @@ 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')) { 
+                    $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) {
@@ -8641,14 +8782,18 @@ 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.
 2. (Optional) institutional type which determined the value of the
    default quota.
@@ -8662,33 +8807,35 @@ If the user's status includes multiple t
 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};
@@ -8702,16 +8849,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);
@@ -8720,6 +8876,63 @@ 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: 6
+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.
+
+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) = @_;
+    my $current_disk_usage = 0;
+    my $disk_quota = &get_user_quota($uname,$udom,$context); #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><span 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.',
+                            $disk_quota,$current_disk_usage).
+               '</p>';
+    }
+    return;
+}
+
+###############################################
+
+
+
+
 sub get_secgrprole_info {
     my ($cdom,$cnum,$needroles,$type)  = @_;
     my %sections_count = &get_sections($cdom,$cnum);
@@ -12893,16 +13106,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 
 
@@ -13280,7 +13497,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;
@@ -14078,7 +14295,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
 
@@ -14109,6 +14326,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" => '',
@@ -14361,6 +14580,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 = &recurse_supplemental($cnum,$cdom,$1,$numfiles);
+                        } else {
+                            $numfiles ++;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return ($numfiles,$errors);
+}
+
 sub symb_to_docspath {
     my ($symb) = @_;
     return unless ($symb);
@@ -14566,7 +14809,7 @@ sub create_recaptcha {
     return $captcha->get_options_setter({theme => 'white'})."\n".
            $captcha->get_html($pubkey).
            &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 />';
 }