--- loncom/interface/lonhtmlcommon.pm	2013/05/03 21:57:13	1.344
+++ loncom/interface/lonhtmlcommon.pm	2021/12/31 20:56:46	1.358.2.19.2.1
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common html routines
 #
-# $Id: lonhtmlcommon.pm,v 1.344 2013/05/03 21:57:13 raeburn Exp $
+# $Id: lonhtmlcommon.pm,v 1.358.2.19.2.1 2021/12/31 20:56:46 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -64,9 +64,15 @@ use HTML::Entities();
 use LONCAPA qw(:DEFAULT :match);
 
 sub java_not_enabled {
-   return "\n".'<span class="LC_error">'.
-          &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.').
-          "</span>\n";
+    if (($env{'browser.mobile'}) && ($env{'browser.mobile'} =~ /^ipad|ipod|iphone$/i)) {
+        return "\n".'<span class="LC_error">'.
+               &mt('The required Java applet could not be started, because Java is not supported by your mobile device.').
+               "</span>\n";
+    } else {
+        return "\n".'<span class="LC_error">'.
+               &mt('The required Java applet could not be started. Please make sure to have Java installed and active in your browser.').
+               "</span>\n";
+    }
 }
 
 sub coursepreflink {
@@ -86,7 +92,7 @@ sub raw_href_to_link {
 
 sub entity_encode {
     my ($text)=@_;
-    return &HTML::Entities::encode($text, '<>&"');
+    return &HTML::Entities::encode($text, '\'<>&"');
 }
 
 sub direct_parm_link {
@@ -170,7 +176,7 @@ sub dragmath_js {
                   function mathedit(textarea, doc) {
                      targetEntry = textarea;
                      targetDoc   = doc;
-                     newwin  = window.open("/adm/dragmath/applet/$popup.html","","width=565,height=500,resizable");
+                     newwin  = window.open("/adm/dragmath/$popup.html","","width=565,height=500,resizable");
                   }
                 // ]]>
                 </script>
@@ -401,7 +407,7 @@ sub textbox {
 ##############################################
 ##############################################
 sub checkbox {
-    my ($name,$checked,$value) = @_;
+    my ($name,$checked,$value,$special) = @_;
     my $Str = '<input type="checkbox" name="'.$name.'" ';
     if (defined($value)) {
         $Str .= 'value="'.$value.'"';
@@ -409,7 +415,7 @@ sub checkbox {
     if ($checked) {
         $Str .= ' checked="checked"';
     }
-    $Str .= ' />';
+    $Str .= $special.' />';
     return $Str;
 }
 
@@ -476,7 +482,36 @@ the date/time fields are left empty.
 =item $state
 
 Specifies the initial state of the form elements.  Either 'disabled' or empty.
-Defaults to empty, which indiciates the form elements are not disabled. 
+Defaults to empty, which indicates the form elements are not disabled.
+
+=item $no_hh_mm_ss
+
+If true, text boxes for hours, minutes and seconds are omitted.
+
+=item $defhour
+
+Default value for hours (a default of 0 is used otherwise).
+
+=item $defmin
+
+Default value for minutes (a default of 0 is used otherwise).
+
+=item defsec
+
+Default value for seconds (a default of 0 is used otherwise).
+
+=item $nolink
+
+If true, a "Select calendar" link (to pop-up a calendar) is not displayed
+to the right of the items.
+
+=item $no_mm_ss
+
+If true, text boxes for minutes and seconds are omitted.
+
+=item $no_ss
+
+If true, text boxes for seconds are omitted.
 
 =back
 
@@ -490,7 +525,7 @@ The method used to restrict user input w
 ##############################################
 sub date_setter {
     my ($formname,$dname,$currentvalue,$special,$includeempty,$state,
-        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink) = @_;
+        $no_hh_mm_ss,$defhour,$defmin,$defsec,$nolink,$no_mm_ss,$no_ss) = @_;
     my $now = time;
 
     my $tzname;
@@ -499,6 +534,8 @@ sub date_setter {
 
     if (! defined($state) || $state ne 'disabled') {
         $state = '';
+    } else {
+        $state = 'disabled="disabled"';
     }
     if (! defined($no_hh_mm_ss)) {
         $no_hh_mm_ss = 0;
@@ -634,7 +671,7 @@ ENDJS
     my $minuteselector = qq{<input type="text" name="$dname\_minute" $special $state value="$min" size="3" />};
     my $secondselector= qq{<input type="text" name="$dname\_second" $special $state value="$sec" size="3" />};
     my $cal_link;
-    if (!$nolink) {
+    unless (($nolink) || ($state eq 'disabled')) {
         $cal_link = qq{<a href="javascript:$dname\_opencalendar()">};
     }
     #
@@ -643,17 +680,24 @@ ENDJS
         $result .= &mt('[_1] [_2] [_3] ',
                        $monthselector,$dayselector,$yearselector).
                    $tzone;
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+    } elsif ($no_mm_ss) {
+        $result .= &mt('[_1] [_2] [_3] [_4]',
+                      $monthselector,$dayselector,$yearselector,
+                      $hourselector).
+                   $tzone;
+    } elsif ($no_ss) {
+        $result .= &mt('[_1] [_2] [_3] [_4] [_5]m',
+                      $monthselector,$dayselector,$yearselector,
+                      $hourselector,$minuteselector).
+                   $tzone;
     } else {
         $result .= &mt('[_1] [_2] [_3] [_4] [_5]m [_6]s ',
                       $monthselector,$dayselector,$yearselector,
                       $hourselector,$minuteselector,$secondselector).
                    $tzone;
-        if (!$nolink) {
-            $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
-        }
+    }
+    unless (($nolink) || ($state eq 'disabled')) {
+        $result .= &mt('[_1]Select Date[_2]',$cal_link,'</a>');
     }
     $result .= "</span>\n<!-- end $dname date setting form -->\n";
     return $result;
@@ -804,13 +848,14 @@ parameter setting wizard.
 ##############################################
 sub pjump_javascript_definition {
     my $Str = <<END;
-    function pjump(type,dis,value,marker,ret,call,hour,min,sec) {
+    function pjump(type,dis,value,marker,ret,call,hour,min,sec,extra) {
         openMyModal("/adm/rat/parameter.html?type="+escape(type)
                  +"&value="+escape(value)+"&marker="+escape(marker)
                  +"&return="+escape(ret)
                  +"&call="+escape(call)+"&name="+escape(dis)
                  +"&defhour="+escape(hour)+"&defmin="+escape(min)
-                 +"&defsec="+escape(sec)+"&modal=1",350,350,'no');
+                 +"&defsec="+escape(sec)+"&extra="+escape(extra)
+                 +"&modal=1",350,350,'no');
     }
 END
     return $Str;
@@ -965,10 +1010,13 @@ Inputs
 
 =item $number_to_do The total number of items being processed.
 
+=item $preamble Optional HTML to display before the progress bar.
+
 =back
 
 Returns a hash containing the progress state data structure.
-
+If $number_to_do is zero or null, an indeterminate progress bar will
+be used.
 
 =item &Update_PrgWin()
 
@@ -1038,20 +1086,20 @@ Returns: none
 
 # Create progress
 sub Create_PrgWin {
-    my ($r,$number_to_do)=@_;
+    my ($r,$number_to_do,$preamble)=@_;
     my %prog_state;
     $prog_state{'done'}=0;
     $prog_state{'firststart'}=&Time::HiRes::time();
     $prog_state{'laststart'}=&Time::HiRes::time();
     $prog_state{'max'}=$number_to_do;
-    &Apache::loncommon::LCprogressbar($r); 
+    &Apache::loncommon::LCprogressbar($r,$prog_state{'max'},$preamble); 
     return %prog_state;
 }
 
 # update progress
 sub Update_PrgWin {
     my ($r,$prog_state,$displayString)=@_;
-    &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString);
+    &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
@@ -1101,7 +1149,7 @@ sub Increment_PrgWin {
     if ($$prog_state{'max'}) {
        $percent=int(100.*$current/$$prog_state{'max'});
     }
-    &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo);
+    &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo,$$prog_state{'max'});
     $$prog_state{'laststart'}=&Time::HiRes::time();
 }
 
@@ -1116,9 +1164,9 @@ sub Close_PrgWin {
 # ------------------------------------------------------- Puts directory header
 
 sub crumbs {
-    my ($uri,$target,$prefix,$form,$skiplast)=@_;
+    my ($uri,$target,$prefix,$form,$skiplast,$onclick)=@_;
 # You cannot crumbnify uploaded or adm resources
-    if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Group Content)'); }
+    if ($uri=~/^\/*(uploaded|adm)\//) { return &mt('(Internal Course/Community Content)'); }
     if ($target) {
         $target = ' target="'.
                   &Apache::loncommon::escape_single($target).'"';
@@ -1138,13 +1186,19 @@ sub crumbs {
             } else {
                 $path.='/'; 
             }
+            if ($path eq '/res/') {
+                unless (&Apache::lonnet::allowed('bre',$path)) {
+                    $output.="$dir/";
+                    next;
+                }
+            }
             my $href_path = &HTML::Entities::encode($path,'<>&"');
             &Apache::loncommon::inhibit_menu_check(\$href_path);
             if ($form) {
                 my $href = 'javascript:'.$form.".action='".$href_path."';".$form.'.submit();';
-                $output.=qq{<a href="$href"$target>$dir</a>/};
+                $output.=qq{<a href="$href"$onclick$target>$dir</a>/};
             } else {
-                $output.=qq{<a href="$href_path"$target>$dir</a>/};
+                $output.=qq{<a href="$href_path"$onclick$target>$dir</a>/};
             }
         }
     } else {
@@ -1215,17 +1269,18 @@ sub htmlareaheaders {
 ENDEDITOR
 	}
     $s.=(<<ENDJQUERY);
-<script type="text/javascript" src="/adm/jQuery/js/jquery-1.6.2.min.js"></script>
-<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.8.16.custom.min.js"></script>
-<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.8.16.custom.css" />
+<script type="text/javascript" src="/adm/jQuery/js/jquery-3.2.1.min.js"></script>
+<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.12.1.custom.min.js"></script>
+<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.12.1.custom.min.css" />
 <script type="text/javascript" src="/adm/jpicker/js/jpicker-1.1.6.min.js" >
 </script>
 <link rel="stylesheet" type="text/css" href="/adm/jpicker/css/jPicker-1.1.6.min.css" />
-<script type="text/javascript" src="/adm/countdown/js/jquery.countdown.js"></script>
+<script type="text/javascript" src="/adm/countdown/js/jquery.countdown.min.js"></script>
 <link rel="stylesheet" type="text/css" href="/adm/countdown/css/jquery.countdown.css" />
 
 <script type="text/javascript" src="/adm/spellchecker/js/jquery.spellchecker.min.js"></script>
 <link rel="stylesheet" type="text/css" href="/adm/spellchecker/css/spellchecker.css" />
+<script type="text/javascript" src="/adm/nicescroll/jquery.nicescroll.min.js"></script>
 
 ENDJQUERY
 	return $s;
@@ -1259,7 +1314,8 @@ sub htmlareaselectactive {
     my ($args) = @_; 
     unless (&htmlareabrowser()) { return ''; }
     my $output='<script type="text/javascript" defer="defer">'."\n"
-              .'// <![CDATA['."\n";
+              .'// <![CDATA['."\n"
+              .'//<!-- BEGIN LON-CAPA Internal'."\n";
     my $lang = &htmlarea_lang();
     my $fullpage = 'false';
     my ($dragmath_prefix,$dragmath_helpicon,$dragmath_whitespace);
@@ -1298,6 +1354,41 @@ sub htmlareaselectactive {
     }
     
     function startRichEditor(id) {
+        // fix character entities inside <m>
+        // NOTE: this is not fixing characters inside <parse>
+        // NOTE: < and > inside <chem> should fix automatically because there should not be a letter after <.
+        var ta = document.getElementById(id);
+        var value = ta.value;
+        var in_m = false; // in the m element
+        var in_text = false; // in the text inside the m element
+        var im = -1; // position of <m>
+        var it = -1; // position of the text inside
+        for (var i=0; i<value.length; i++) {
+            if (value.substr(i, 2) == "<m") {
+                // ignore previous <m> if found twice
+                in_m = true;
+                in_text = false;
+                im = i;
+                it = -1;
+            } else if (in_m) {
+                if (!in_text) {
+                    if (value.charAt(i) == ">") {
+                        in_text = true;
+                        it = i+1;
+                    }
+                } else if (value.substr(i, 4) == "</m>") {
+                    in_m = false;
+                    var text = value.substr(it, i-it);
+                    var l1 = text.length;
+                    text = text.replace(/</g, "&lt;");
+                    text = text.replace(/>/g, "&gt;");
+                    var l2 = text.length;
+                    value = value.substr(0, it) + text + "</m>" + value.substr(i+4);
+                    i = i + (l2-l1);
+                }
+            }
+        }
+        ta.value = value;
     	CKEDITOR.replace(id, 
     		{
     			customConfig: "/ckeditor/loncapaconfig.js",
@@ -1309,6 +1400,68 @@ sub htmlareaselectactive {
     
     function destroyRichEditor(id) {
     	CKEDITOR.instances[id].destroy();
+        // replace character entities &lt; and &gt; in <m> and <chem>
+        // and "&amp;fctname(" by "&fctname("
+        // and the quotes inside functions: "&fct(1, &quot;a&quot;)" -> "&fct(1, "a")"
+        var ta = document.getElementById(id);
+        var value = ta.value;
+        var in_element = false; // in the m or chem element
+        var tagname = ""; // m or chem
+        var in_text = false; // in the text inside the element
+        var im = -1; // position of start tag
+        var it = -1; // position of the text inside
+        for (var i=0; i<value.length; i++) {
+            if (value.substr(i, 2) == "<m" || value.substr(i, 5) == "<chem") {
+                // ignore previous tags if found twice
+                in_element = true;
+                if (value.substr(i, 2) == "<m")
+                    tagname = "m";
+                else
+                    tagname = "chem";
+                in_text = false;
+                im = i;
+                it = -1;
+            } else if (in_element) {
+                if (!in_text) {
+                    if (value.charAt(i) == ">") {
+                        in_text = true;
+                        it = i+1;
+                    }
+                } else if (value.substr(i, 3+tagname.length) == "</"+tagname+">") {
+                    in_element = false;
+                    var text = value.substr(it, i-it);
+                    var l1 = text.length;
+                    text = text.replace(/&lt;/g, "<");
+                    text = text.replace(/&gt;/g, ">");
+                    var l2 = text.length;
+                    value = value.substr(0, it) + text + value.substr(i);
+                    i = i + (l2-l1);
+                }
+            }
+        }
+        // fix function names
+        value = value.replace(/&amp;([a-zA-Z_]+)\(/g, "&$1(");
+        // fix quotes in functions
+        var pos_next_fct = value.search(/&[a-zA-Z_]+\(/);
+        var depth = 0;
+        for (var i=0; i<value.length; i++) {
+            if (i == pos_next_fct) {
+                depth++;
+                var sub = value.substring(i+1);
+                var pos2 = sub.search(/&[a-zA-Z_]+\(/);
+                if (pos2 == -1)
+                    pos_next_fct = -1;
+                else
+                    pos_next_fct = i + 1 + pos2;
+            } else if (depth > 0) {
+                if (value.charAt(i) == ")")
+                    depth--;
+                else if (value.substr(i, 6) == "&quot;")
+                    value = value.substr(0, i) + "\"" + value.substr(i+6);
+            }
+        }
+        // replace the text value
+        ta.value = value;
     }
     
     function editorHandler(event) {
@@ -1498,6 +1651,7 @@ JAVASCRIPT
 
     }
     $output.="\nwindow.status='Activated Editfields';\n"
+            .'// END LON-CAPA Internal -->'."\n"
             .'// ]]>'."\n"
             .'</script>';
     return $output;
@@ -1540,7 +1694,7 @@ sub show_return_link {
             (($env{'request.noversionuri'}=~/^\/adm\//) &&
              ($env{'request.noversionuri'}!~/^\/adm\/wrapper\//) &&
              ($env{'request.noversionuri'}!~
-              m{^/adm/.*/(smppg|bulletinboard)($|\?)})
+              m{^/adm/.*/(smppg|bulletinboard|ext\.tool)($|\?)})
            ));
 }
 
@@ -1615,12 +1769,19 @@ A link to help for the component will be
 All inputs can be undef without problems.
 
 Inputs: $component (the text on the right side of the breadcrumbs trail),
-        $component_help
+        $component_help (the help item filename (without .tex extension).
         $menulink (boolean, controls whether to include a link to /adm/menu)
         $helplink (if 'nohelp' don't include the orange help link)
         $css_class (optional name for the class to apply to the table for CSS)
         $no_mt (optional flag, 1 if &mt() is _not_ to be applied to $component
            when including the text on the right.
+        $CourseBreadcrumbs (optional flag, 1 if &breadcrumbs called from &docs_breadcrumbs,
+           because breadcrumbs are being)
+        $topic_help (optional help item to be displayed on right side of the breadcrumbs 
+           row, using loncommon::help_open_topic() to generate the link.
+        $topic_help_text (text to include in the link in the optional help item 
+           on the right side of the breadcrumbs row.
+
 Returns a string containing breadcrumbs for the current page.
 
 =item &clear_breadcrumbs()
@@ -1648,7 +1809,7 @@ returns: nothing
     
     sub breadcrumbs {
         my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, 
-            $CourseBreadcrumbs) = @_;
+            $CourseBreadcrumbs,$topic_help,$topic_help_text) = @_;
         #
         $css_class ||= 'LC_breadcrumbs';
 
@@ -1664,6 +1825,16 @@ returns: nothing
         # The first one should be the course or a menu link
         if (!defined($menulink)) { $menulink=1; }
         if ($menulink) {
+            if ($env{'request.course.id'}) {
+                my ($menucoll,$deeplinkmenu,$menuref) = &Apache::loncommon::menucoll_in_effect();
+                if (($menucoll) && (ref($menuref) eq 'HASH')) {
+                    if ($menuref->{'main'} eq 'n') {
+                       undef($menulink);
+                    }
+                }
+            }
+        }
+        if ($menulink) {
             my $description = 'Menu';
             my $no_mt_descr = 0;
             if ((exists($env{'request.course.id'})) && 
@@ -1673,7 +1844,7 @@ returns: nothing
                     $env{'course.'.$env{'request.course.id'}.'.description'};
                 $no_mt_descr = 1;
                 if ($env{'request.noversionuri'} =~ 
-                    m{^/public/($match_domain)/($match_courseid)/syllabus$}) {
+                    m{^/?public/($match_domain)/($match_courseid)/syllabus$}) {
                     unless (($env{'course.'.$env{'request.course.id'}.'.domain'} eq $1) &&
                             ($env{'course.'.$env{'request.course.id'}.'.num'} eq $2)) {
                         $description = 'Menu';
@@ -1697,11 +1868,15 @@ returns: nothing
         my $links;
         if ((&show_return_link) && (!$CourseBreadcrumbs) && (ref($last) eq 'HASH')) {
             my $alttext = &mt('Go Back');
+            my $hashref = { href => '/adm/flip?postdata=return:',
+                            title => &mt('Back to most recent content resource'),
+                            class => 'LC_menubuttons_link',
+                          };
+            if ($env{'request.noversionuri'} eq '/adm/searchcat') {
+                $hashref->{'target'} = '_top'; 
+            }
             $links=&htmltag( 'a','<img src="/res/adm/pages/tolastloc.png" alt="'.$alttext.'" class="LC_icon" />',
-                            { href => '/adm/flip?postdata=return:',
-                              title => &mt('Back to most recent content resource'),
-                              class => 'LC_menubuttons_link',
-                            });
+                             $hashref);
             $links=&htmltag('li',$links);
         }
         $links.= join "", 
@@ -1751,6 +1926,10 @@ returns: nothing
                                                          $component_help,
                                                          $faq,$bug);
         }
+        if ($topic_help && $topic_help_text) {
+           $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'',
+                                                             undef,600);
+        }
         #
 
 		
@@ -1763,7 +1942,7 @@ returns: nothing
         }
 
 
-        if ($component) {
+        if (($component) || ($topic_help && $topic_help_text)) {
             $links = &htmltag('span', 
                              ( $no_mt ? $component : mt($component) ).
                              ( $icons ? $icons : '' ),
@@ -1866,6 +2045,16 @@ returns: nothing
         undef(%tools);
     }
 
+=item &current_breadcrumb_tools()
+
+returns: a hash containing the current breadcrumb tools.
+
+=cut
+
+    sub current_breadcrumb_tools {
+        return %tools;
+    }
+
 =item &render_tools(\$breadcrumbs)
 
 Creates html for breadcrumb tools (categories navigation and tools) and inserts 
@@ -1932,11 +2121,11 @@ sub docs_breadcrumbs {
         my $foldername=shift(@folders);
         if ($folderpath) {$folderpath.='&';}
         $folderpath.=$folder.'&'.$foldername;
-        my $url;
+        my $url = $env{'request.use_absolute'};
         if ($allowed) {
-            $url = '/adm/coursedocs?folderpath=';
+            $url .= '/adm/coursedocs?folderpath=';
         } else {
-            $url = '/adm/supplemental?folderpath=';
+            $url .= '/adm/supplemental?folderpath=';
         }
         $url .= &escape($folderpath);
         my $name=&unescape($foldername);
@@ -1957,7 +2146,7 @@ sub docs_breadcrumbs {
             }
         }
         if ($folder eq 'supplemental') {
-            $name = &mt('Supplemental '.$crstype.' Contents');
+            $name = &mt('Supplemental Content');
         }
         if ($contenteditor) {
             $plain.=$name.' &gt; ';
@@ -2228,10 +2417,10 @@ sub course_selection {
     if ($totcodes > 0) {
         my $numtitles = @$codetitles;
         if ($numtitles > 0) {
-            $output .= '<label><input type="radio" name="coursepick" value="category" onclick="coursePick(this.form);alert('."'".&mt('Choose categories, from left to right')."'".')" />'.&mt('Pick courses by category:').'</label><br />';
+            $output .= '<label><input type="radio" name="coursepick" value="category" onclick="coursePick(this.form);alert('."'".&html_escape(&mt('Choose categories, from left to right'))."'".')" />'.&mt('Pick courses by category:').'</label><br />';
             $output .= '<table><tr><td>'.$$codetitles[0].'<br />'."\n".
                '<select name="'.$standardnames->[0].
-               '" onChange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
+               '" onchange="setPick(this.form);courseSet('."'$$codetitles[0]'".')">'."\n".
                ' <option value="-1" />Select'."\n";
             my @items = ();
             my @longitems = ();
@@ -2261,7 +2450,7 @@ sub course_selection {
             for (my $i=1; $i<$numtitles; $i++) {
                 $output .= '<td>'.$$codetitles[$i].'<br />'."\n".
                           '<select name="'.$standardnames->[$i].
-                          '" onChange="courseSet('."'$$codetitles[$i]'".')">'."\n".
+                          '" onchange="courseSet('."'$$codetitles[$i]'".')">'."\n".
                           '<option value="-1">&lt;-Pick '.$$codetitles[$i-1].'</option>'."\n".
                           '</select>'."\n".
                           '</td>';
@@ -2361,9 +2550,9 @@ sub course_custom_roles {
 
 
 sub resource_info_box {
-   my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp)=@_;
+   my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp,$divforres)=@_;
    my $return='';
-   if ($stuvcurrent ne '') {
+   if (($stuvcurrent ne '') || ($divforres)) {
        $return = '<div class="LC_left_float">';
    }
    if ($symb) {
@@ -2392,12 +2581,59 @@ sub resource_info_box {
     } else {
        $return='<p><span class="LC_error">'.&mt('No context provided.').'</span></p>';
     }
-    if ($stuvcurrent ne '') {
+    if (($stuvcurrent ne '') || ($divforres)) {
         $return .= '</div>';
     }
     return $return;
 }
 
+# display_usage
+# 
+# Generates a div containing a block, filled to show percentage of current quota used
+#
+# Quotas available for user portfolios, group portfolios, authoring spaces, and course
+# content stored directly within a course (i.e., excluding published content).
+#
+
+sub display_usage {
+    my ($current_disk_usage,$disk_quota,$context) = @_;
+    my $usage = $current_disk_usage/1024;
+    my $quota = $disk_quota/1024;
+    my $percent;
+    if ($disk_quota == 0) {
+        $percent = 100.0;
+    } else {
+        $percent = 100*($current_disk_usage/$disk_quota);
+    }
+    $usage = sprintf("%.2f",$usage);
+    $quota = sprintf("%.2f",$quota);
+    $percent = sprintf("%.0f",$percent);
+    my ($color,$cssclass);
+    if ($percent <= 60) {
+        $color = '#00A000';
+    } elsif ($percent > 60 && $percent < 90) {
+        $color = '#FFD300';
+        $cssclass = 'class="LC_warning"';
+    } elsif( $percent >= 90) {
+        $color = '#FF0000';
+        $cssclass = 'class="LC_error"';
+    }
+    my $prog_width = $percent;
+    if ($prog_width > 100) {
+        $prog_width = 100;
+    }
+    my $display = 'block';
+    if ($context eq 'authoring') {
+        $display = 'inline';
+    }
+    return '
+  <div id="meter1" align="left" style="display:'.$display.'" '.$cssclass.'>'.&mt('Currently using [_1] of the [_2] available.',$usage.' MB <span style="font-weight:bold;">('.$percent.'%)</span>',$quota.' MB')."\n".
+'   <div id="meter2" style="display:block; margin-top:3px; margin-bottom:3px; margin-left:0px; margin-right:0px; width:400px; border:1px solid #000000; height:10px;">'."\n".
+'    <div id="meter3" style="display:block; background-color:'.$color.'; width:'.$prog_width.'%; height:10px; color:#000000; margin:0px;"></div>'."\n".
+'   </div>'."\n".
+'  </div>';
+}
+
 ##############################################
 ##############################################
 
@@ -2642,10 +2878,12 @@ sub set_form_elements {
 
 sub file_submissionchk_js {
     my ($turninpaths,$multiples) = @_;
-    my $overwritewarn = &mt('File(s) you uploaded for your submission will overwrite existing file(s) submitted for this item').'\\n'.
+    my $overwritewarn = &mt('File(s) you uploaded for your submission will overwrite existing file(s) submitted for this item')."\n".
                       &mt('Continue submission and overwrite the file(s)?');
-    my $delfilewarn = &mt('You have indicated you wish to remove some files previously included in your submission.').'\\n'.
+    &js_escape(\$overwritewarn);
+    my $delfilewarn = &mt('You have indicated you wish to remove some files previously included in your submission.')."\n".
                       &mt('Continue submission with these files removed?');
+    &js_escape(\$delfilewarn);
     my ($turninpathtext,$multtext,$arrayindexofjs);
     if (ref($turninpaths) eq 'HASH') {
         foreach my $key (sort(keys(%{$turninpaths}))) {
@@ -2886,7 +3124,7 @@ ENDSCRIPT
 ##############################################
 
 sub resize_scrollbox_js {
-    my ($context,$tabidstr) = @_;
+    my ($context,$tabidstr,$tid) = @_;
     my (%names,$paddingwfrac,$offsetwfrac,$offsetv,$minw,$minv);
     if ($context eq 'docs') {
         %names = (
@@ -2896,7 +3134,7 @@ sub resize_scrollbox_js {
                    scroll => 'contentscroll',
                    boxh   => 'contenteditor',
                  );
-        $paddingwfrac = 0.09; 
+        $paddingwfrac = 0.09;
         $offsetwfrac = 0.015;
         $offsetv = 20;
         $minw = 250;
@@ -2922,9 +3160,11 @@ window.onresize=callResize;
 
 ';
     if ($context eq 'docs') {
-        $output .= '
-var activeTab;
-';
+        if ($env{'form.active'}) {
+            $output .= "\nvar activeTab = '$env{'form.active'}$tid';\n";
+        } else {
+            $output .= "\nvar activeTab = '';\n";
+        }
     }
     $output .=  <<"FIRST";
 
@@ -2935,6 +3175,7 @@ function resize_scrollbox(scrollboxname,
     var scrolltableid = 'table_'+scrollboxname;
     var scrollbox;
     var scrolltable;
+    var ismobile = '$env{'browser.mobile'}';
 
     if (document.getElementById("$names{'boxw'}") == null) {
         return;
@@ -2971,6 +3212,7 @@ FIRST
     }
     $output .= <<"SECOND";
     var listwchange;
+    var scrollchange;
     if (chkw == 1) {
         var boxw = document.getElementById("$names{'boxw'}").offsetWidth;
         var itemw;
@@ -2982,6 +3224,7 @@ FIRST
 
         var scrollboxw = scrollbox.offsetWidth;
         var scrollboxscrollw = scrollbox.scrollWidth;
+        var scrollstart = scrollboxw;
 
         var offsetw = parseInt(vpw * $offsetwfrac);
         var paddingw = parseInt(vpw * $paddingwfrac);
@@ -3056,17 +3299,31 @@ PARAMSONE
             }
         }
 
+        if (newscrollboxw != scrollboxw) {
+            scrollchange = 1;
+        }
+
         if (itemid.offsetWidth != itemwstart) {
             listwchange = 1;
         }
     }
     if ((chkh == 1) || (listwchange)) {
-        var primaryheight = document.getElementById('LC_nav_bar').offsetHeight;
-        var secondaryheight;
+        var itemid = document.getElementById("$names{'item'}");
+        if (itemid != null) {
+            itemh = itemid.offsetHeight;
+        }
+        var primaryheight = 0;
+        if (document.getElementById('LC_nav_bar') != null) {
+            primaryheight = document.getElementById('LC_nav_bar').offsetHeight;
+        }
+        var secondaryheight = 0;
         if (document.getElementById('LC_secondary_menu') != null) { 
             secondaryheight = document.getElementById('LC_secondary_menu').offsetHeight;
         }
-        var crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight;
+        var crumbsheight = 0;
+        if (document.getElementById('LC_breadcrumbs') != null) {
+            crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight;
+        }
         var dccidheight = 0;
         if (document.getElementById('dccid') != null) {
             dccidheight = document.getElementById('dccid').offsetHeight;
@@ -3081,6 +3338,7 @@ PARAMSONE
 
         var scrollboxheight = scrollbox.offsetHeight;
         var scrollboxscrollheight = scrollbox.scrollHeight;
+        var scrollboxh = scrollboxheight;
 
         var minvscrollbox = $minv;
         var offsetv = $offsetv;
@@ -3111,6 +3369,13 @@ PARAMSONE
                 scrollbox.style.height = newscrollheight+"px";
             }
         }
+        var newscrollboxh = scrollbox.offsetHeight;
+        if (scrollboxh != newscrollboxh) {
+            scrollchange = 1;
+        }
+    }
+    if (ismobile && scrollchange) {
+        \$("#div_$names{'scroll'}").getNiceScroll().onResize();
     }
     return;
 }
@@ -3129,15 +3394,26 @@ THIRD
 ##############################################
 
 sub javascript_jumpto_resource {
-    my $confirm_switch = &mt("Editing requires switching to the resource's home server.").'\n'.
+    my $confirm_switch = &mt("Editing requires switching to the resource's home server.")."\n".
                          &mt('Switch server?');
+    &js_escape(\$confirm_switch);
     return (<<ENDUTILITY)
 
 function go(url) {
    if (url!='' && url!= null) {
        currentURL = null;
        currentSymb= null;
-       window.location.href=url;
+       var lcHostname = setLCHost();
+       if (lcHostname!='' && lcHostname!= null) {
+           var RegExp = /^https?\:/;
+           if (RegExp.test(url)) {
+               window.location.href=url;
+           } else {
+               window.location.href=lcHostname+url;
+           }
+       } else {
+           window.location.href=url;
+       }
    }
 }
 
@@ -3156,8 +3432,8 @@ ENDUTILITY
 
 sub jump_to_editres {
     my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$folderpath,
-        $title,$idx,$suppurl,$todocs) = @_;
-    my $jscall;
+        $title,$hostname,$idx,$suppurl,$todocs,$suppanchor) = @_;
+    my ($jscall,$anchor,$usehttp,$usehttps,$is_ext);
     if ($switchserver) {
         if ($home) {
             $cfile = '/adm/switchserver?otherserver='.$home.'&amp;role='.
@@ -3173,11 +3449,48 @@ sub jump_to_editres {
             if ($forcereg) {
                 $cfile .= '&amp;register=1';
             }
-            $jscall = "need_switchserver('$cfile');";
+            $jscall = "need_switchserver('".&Apache::loncommon::escape_single($cfile)."');";
         }
     } else {
         unless ($cfile =~ m{^/priv/}) {
+            if ($cfile =~ m{^(/adm/wrapper/ext/([^#]+))(?:|#([^#]+))$}) {
+                $cfile = $1;
+                my $extlink = $2;
+                $anchor = $3;
+                $is_ext = 1;
+                if (($extlink !~ /^https:/) && ($ENV{'SERVER_PORT'} == 443)) {
+                    unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+                        $usehttp = 1;
+                    }
+                } elsif ($env{'request.use_absolute'}) {
+                    if ($env{'request.use_absolute'} =~ m{^https://}) {
+                        $usehttps = 1;
+                    }
+                }
+            } elsif ($cfile =~ m{^/?public/($match_domain)/($match_courseid)/syllabus}) {
+                if ($ENV{'SERVER_PORT'} == 443) {
+                    my ($cdom,$cnum) = ($1,$2);
+                    if (($env{'request.course.id'}) &&
+                        ($env{'course.'.$env{'request.course.id'}.'.num'} eq $cnum) &&
+                        ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $cdom)) {
+                        if ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://}) {
+                            unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) {
+                                $usehttp = 1;
+                            }
+                        }
+                    }
+                } elsif ($env{'request.use_absolute'}) {
+                    if ($env{'request.use_absolute'} =~ m{^https://}) {
+                        $usehttps = 1;
+                    }
+                }
+            }
             if ($symb) {
+                if ($anchor ne '') {
+                    if ($symb =~ m{^([^#]+)\Q#$anchor\E$}) {
+                        $symb = $1.&escape(&escape('#')).$anchor;
+                    }
+                }
                 $cfile .= (($cfile=~/\?/)?'&amp;':'?')."symb=$symb";
             } elsif ($folderpath) {
                 $cfile .= (($cfile=~/\?/)?'&amp;':'?').
@@ -3196,15 +3509,32 @@ sub jump_to_editres {
             }
             if ($forceedit) {
                 $cfile .= (($cfile=~/\?/)?'&amp;':'?').'forceedit=1';
+                if ($usehttps) {
+                    $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile;
+                }
+            } elsif ($usehttp) {
+                if ($hostname ne '') {
+                    $cfile = 'http://'.$hostname.(($cfile =~ /^\//)? '':'/').$cfile;
+                }
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'usehttp=1';
+            } elsif ($usehttps) {
+                $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile;
             }
             if ($forcereg) {
                 $cfile .= (($cfile=~/\?/)?'&amp;':'?').'register=1';
             }
             if ($todocs) {
-               $cfile .= (($cfile=~/\?/)?'&amp;':'?').'todocs=1';
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'todocs=1';
+            }
+            if ($suppanchor ne '') {
+                $cfile .= (($cfile=~/\?/)?'&amp;':'?').'anchor='.
+                          &HTML::Entities::encode($suppanchor,'"<>&');
             }
         }
-        $jscall = "go('$cfile')";
+        if ($anchor ne '') {
+            $cfile .= '#'.$anchor;
+        }
+        $jscall = "go('".&Apache::loncommon::escape_single($cfile)."')";
     }
     return $jscall;
 }
@@ -3215,15 +3545,18 @@ sub jump_to_editres {
 # javascript_valid_email
 #
 # Generates javascript to validate an e-mail address.
-# Returns a javascript function which accetps a form field as argumnent, and
+# Returns a javascript function which accepts a form field as argument, and
 # returns false if field.value does not satisfy two regular expression matches
 # for a valid e-mail address.  Backwards compatible with old browsers without
 # support for javascript RegExp (just checks for @ in field.value in this case). 
 
 sub javascript_valid_email {
     my $scripttag .= <<'END';
-function validmail(field) {
+function validmail(field,suffix) {
     var str = field.value;
+    if (suffix != '' && suffix != undefined) {
+        str += suffix;
+    }
     if (window.RegExp) {
         var reg1str = "(@.*@)|(\\.\\.)|(@\\.)|(\\.@)|(^\\.)";
         var reg2str = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$"; //"
@@ -3271,7 +3604,7 @@ END
 sub htmltag{
     return
         qq|<$_[0]|
-        . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys %{ $_[2] } )
+        . join( '', map { qq| $_="${$_[2]}{$_}"| if ${$_[2]}{$_} } keys(%{ $_[2] }) )
         . ($_[1] ? qq|>$_[1]</$_[0]>| : qq|/>|). "\n";
 };
 
@@ -3534,6 +3867,7 @@ add_item_funclist
 Inputs: ./.
 
 Returns: HTML code with function list end
+
 =cut
 
 sub end_funclist {
@@ -3577,6 +3911,8 @@ sub funclist_from_array {
 
 =pod
 
+=over
+
 =item &actionbox( \@array )
 
 Constructs a XHTML list from \@array with the first item being visually
@@ -3584,7 +3920,7 @@ highlighted and set to the value 'Action
 
 The actionlist is used to offer contextual actions, mostly at the bottom
 of a page, on which the outcome of an processed action is shown,
-e.g. a file operation in Construction Space.
+e.g. a file operation in Authoring Space.
 
 =over
 
@@ -3594,7 +3930,7 @@ A reference to the array containing text
 
 =back
  
-Returns: XHTML div as string. 
+Returns: XHTML div as string.
 
 =back