--- loncom/interface/loncommon.pm	2011/12/08 02:59:37	1.1032
+++ loncom/interface/loncommon.pm	2012/04/25 21:22:01	1.1073
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.1032 2011/12/08 02:59:37 www Exp $
+# $Id: loncommon.pm,v 1.1073 2012/04/25 21:22:01 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -154,6 +154,8 @@ sub ssi_with_retries {
 # ----------------------------------------------- Filetypes/Languages/Copyright
 my %language;
 my %supported_language;
+my %latex_language;		# For choosing hyphenation in <transl..>
+my %latex_language_bykey;	# for choosing hyphenation from metadata
 my %cprtag;
 my %scprtag;
 my %fe; my %fd; my %fm;
@@ -186,11 +188,15 @@ BEGIN {
             while (my $line = <$fh>) {
                 next if ($line=~/^\#/);
                 chomp($line);
-                my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$line));
+                my ($key,$two,$country,$three,$enc,$val,$sup,$latex)=(split(/\t/,$line));
                 $language{$key}=$val.' - '.$enc;
                 if ($sup) {
                     $supported_language{$key}=$sup;
                 }
+		if ($latex) {
+		    $latex_language_bykey{$key} = $latex;
+		    $latex_language{$two} = $latex;
+		}
             }
             close($fh);
         }
@@ -1186,7 +1192,7 @@ sub help_open_topic {
     my ($topic, $text, $stayOnPage, $width, $height, $imgid) = @_;
     $text = "" if (not defined $text);
     $stayOnPage = 0 if (not defined $stayOnPage);
-    $width = 350 if (not defined $width);
+    $width = 500 if (not defined $width);
     $height = 400 if (not defined $height);
     my $filename = $topic;
     $filename =~ s/ /_/g;
@@ -1197,7 +1203,9 @@ sub help_open_topic {
     $topic=~s/\W/\_/g;
 
     if (!$stayOnPage) {
-	$link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
+	$link = "javascript:openMyModal('/adm/help/${filename}.hlp',$width,$height,'yes');";
+    } elsif ($stayOnPage eq 'popup') {
+        $link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
     } else {
 	$link = "/adm/help/${filename}.hlp";
     }
@@ -1230,27 +1238,22 @@ sub help_open_topic {
 # This is a quicky function for Latex cheatsheet editing, since it 
 # appears in at least four places
 sub helpLatexCheatsheet {
-    my ($topic,$text,$not_author) = @_;
+    my ($topic,$text,$not_author,$stayOnPage) = @_;
     my $out;
     my $addOther = '';
     if ($topic) {
-	$addOther = '<span>'.&Apache::loncommon::help_open_topic($topic,&mt($text),
-							       undef, undef, 600).
-								   '</span> ';
+	$addOther = '<span>'.&help_open_topic($topic,&mt($text),$stayOnPage, undef, 600).'</span> ';
     }
     $out = '<span>' # Start cheatsheet
 	  .$addOther
           .'<span>'
-	  .&Apache::loncommon::help_open_topic('Greek_Symbols',&mt('Greek Symbols'),
-					       undef,undef,600)
+	  .&help_open_topic('Greek_Symbols',&mt('Greek Symbols'),$stayOnPage,undef,600)
 	  .'</span> <span>'
-	  .&Apache::loncommon::help_open_topic('Other_Symbols',&mt('Other Symbols'),
-					       undef,undef,600)
+	  .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600)
 	  .'</span>';
     unless ($not_author) {
         $out .= ' <span>'
-	       .&Apache::loncommon::help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),
-	                                            undef,undef,600)
+	       .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600)
 	       .'</span>';
     }
     $out .= '</span>'; # End cheatsheet
@@ -1371,7 +1374,7 @@ function helpMenu(target) {
     return;
 }
 function writeHelp(caller) {
-    caller.document.writeln('$start_page<frame name="bannerframe"  src="'+banner_link+'" /><frame name="bodyframe" src="$details_link" /> $end_page')
+    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()
 }
@@ -1745,6 +1748,7 @@ Inputs: $workbook
 
 Returns: $format, a hash reference.
 
+
 =cut
 
 ###############################################################
@@ -2853,6 +2857,7 @@ database which holds them.
 
 Uses global $thesaurus_db_file.
 
+
 =cut
 
 ###############################################################
@@ -3124,12 +3129,12 @@ sub noteswrapper {
 # ------------------------------------------------------------- Aboutme Wrapper
 
 sub aboutmewrapper {
-    my ($link,$username,$domain,$target)=@_;
+    my ($link,$username,$domain,$target,$class)=@_;
     if (!defined($username)  && !defined($domain)) {
         return;
     }
     return '<a href="/adm/'.$domain.'/'.$username.'/aboutme?forcestudent=1"'.
-	($target?' target="$target"':'').' title="'.&mt("View this user's personal information page").'">'.$link.'</a>';
+	($target?' target="'.$target.'"':'').($class?' class="'.$class.'"':'').' title="'.&mt("View this user's personal information page").'">'.$link.'</a>';
 }
 
 # ------------------------------------------------------------ Syllabus Wrapper
@@ -3230,11 +3235,29 @@ sub languagedescription {
 	    ($supported_language{$code}?' ('.&mt('interface available').')':'');
 }
 
+=pod
+
+=item * &plainlanguagedescription
+
+Returns both the plain language description (e.g. 'Creoles and Pidgins, English-based (Other)')
+and the language character encoding (e.g. ISO) separated by a ' - ' string.
+
+=cut
+
 sub plainlanguagedescription {
     my $code=shift;
     return $language{$code};
 }
 
+=pod
+
+=item * &supportedlanguagecode
+
+Returns the supported language code (e.g. sptutf maps to pt) given a language
+code.
+
+=cut
+
 sub supportedlanguagecode {
     my $code=shift;
     return $supported_language{$code};
@@ -3242,6 +3265,35 @@ sub supportedlanguagecode {
 
 =pod
 
+=item * &latexlanguage()
+
+Given a language key code returns the correspondnig language to use
+to select the correct hyphenation on LaTeX printouts.  This is undef if there
+is no supported hyphenation for the language code.
+
+=cut
+
+sub latexlanguage {
+    my $code = shift;
+    return $latex_language{$code};
+}
+
+=pod
+
+=item * &latexhyphenation()
+
+Same as above but what's supplied is the language as it might be stored
+in the metadata.
+
+=cut
+
+sub latexhyphenation {
+    my $key = shift;
+    return $latex_language_bykey{$key};
+}
+
+=pod
+
 =item * &copyrightids() 
 
 returns list of all copyrights
@@ -3938,9 +3990,7 @@ sub findallcourses {
         $udom = $env{'user.domain'};
     }
     if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) {
-        my $extra = &Apache::lonnet::freeze_escape({'skipcheck' => 1});
-        my %roleshash = &Apache::lonnet::dump('roles',$udom,$uname,'.',undef,
-                                              $extra);
+        my %roleshash = &Apache::lonnet::dump('roles',$udom,$uname);
         if (!%roles) {
             %roles = (
                        cc => 1,
@@ -3965,18 +4015,25 @@ sub findallcourses {
             if ($tstart) {
                 next if ($tstart > $now);
             }
-            my ($cdom,$cnum,$sec,$cnumpart,$secpart,$role,$realsec);
+            my ($cdom,$cnum,$sec,$cnumpart,$secpart,$role);
             (undef,$cdom,$cnumpart,$secpart) = split(/\//,$entry);
+            my $value = $trole.'/'.$cdom.'/';
             if ($secpart eq '') {
                 ($cnum,$role) = split(/_/,$cnumpart); 
                 $sec = 'none';
-                $realsec = '';
+                $value .= $cnum.'/';
             } else {
                 $cnum = $cnumpart;
                 ($sec,$role) = split(/_/,$secpart);
-                $realsec = $sec;
+                $value .= $cnum.'/'.$sec;
+            }
+            if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') {
+                unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) {
+                    push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value);
+                }
+            } else {
+                @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value);
             }
-            $courses{$cdom.'_'.$cnum}{$sec} = $trole.'/'.$cdom.'/'.$cnum.'/'.$realsec;
         }
     } else {
         foreach my $key (keys(%env)) {
@@ -3994,11 +4051,19 @@ sub findallcourses {
                     if ($now>$endtime) { $active=0; }
                 }
                 if ($active) {
+                    my $value = $role.'/'.$cdom.'/'.$cnum.'/';
                     if ($sec eq '') {
                         $sec = 'none';
+                    } else {
+                        $value .= $sec;
+                    }
+                    if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') {
+                        unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) {
+                            push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value);
+                        }
+                    } else {
+                        @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value);
                     }
-                    $courses{$cdom.'_'.$cnum}{$sec} = 
-                                     $role.'/'.$cdom.'/'.$cnum.'/'.$sec;
                 }
             }
         }
@@ -4009,7 +4074,7 @@ sub findallcourses {
 ###############################################
 
 sub blockcheck {
-    my ($setters,$activity,$uname,$udom) = @_;
+    my ($setters,$activity,$uname,$udom,$url) = @_;
 
     if (!defined($udom)) {
         $udom = $env{'user.domain'};
@@ -4021,13 +4086,14 @@ sub blockcheck {
     # If uname and udom are for a course, check for blocks in the course.
 
     if (&Apache::lonnet::is_course($udom,$uname)) {
-        my %records = &Apache::lonnet::dump('comm_block',$udom,$uname);
-        my ($startblock,$endblock)=&get_blocks($setters,$activity,$udom,$uname);
-        return ($startblock,$endblock);
+        my ($startblock,$endblock,$triggerblock) = 
+            &get_blocks($setters,$activity,$udom,$uname,$url);
+        return ($startblock,$endblock,$triggerblock);
     }
 
     my $startblock = 0;
     my $endblock = 0;
+    my $triggerblock = '';
     my %live_courses = &findallcourses(undef,$uname,$udom);
 
     # If uname is for a user, and activity is course-specific, i.e.,
@@ -4091,34 +4157,38 @@ sub blockcheck {
             if ($otheruser) {
                 # Resource belongs to user other than current user.
                 # Assemble privs for that user, and check for 'evb' priv.
-                my ($trole,$tdom,$tnum,$tsec);
-                my $entry = $live_courses{$course}{$sec};
-                if ($entry =~ /^cr/) {
-                    ($trole,$tdom,$tnum,$tsec) = 
-                      ($entry =~ m|^(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_username)/?(\w*)$|);
-                } else {
-                    ($trole,$tdom,$tnum,$tsec) = split(/\//,$entry);
-                }
-                my ($spec,$area,$trest,%allroles,%userroles);
-                $area = '/'.$tdom.'/'.$tnum;
-                $trest = $tnum;
-                if ($tsec ne '') {
-                    $area .= '/'.$tsec;
-                    $trest .= '/'.$tsec;
-                }
-                $spec = $trole.'.'.$area;
-                if ($trole =~ /^cr/) {
-                    &Apache::lonnet::custom_roleprivs(\%allroles,$trole,
-                                                      $tdom,$spec,$trest,$area);
-                } else {
-                    &Apache::lonnet::standard_roleprivs(\%allroles,$trole,
-                                                       $tdom,$spec,$trest,$area);
-                }
-                my ($author,$adv) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
-                if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) {
-                    if ($1) {
-                        $no_userblock = 1;
-                        last;
+                my (%allroles,%userroles);
+                if (ref($live_courses{$course}{$sec}) eq 'ARRAY') {
+                    foreach my $entry (@{$live_courses{$course}{$sec}}) { 
+                        my ($trole,$tdom,$tnum,$tsec);
+                        if ($entry =~ /^cr/) {
+                            ($trole,$tdom,$tnum,$tsec) = 
+                                ($entry =~ m|^(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_username)/?(\w*)$|);
+                        } else {
+                           ($trole,$tdom,$tnum,$tsec) = split(/\//,$entry);
+                        }
+                        my ($spec,$area,$trest);
+                        $area = '/'.$tdom.'/'.$tnum;
+                        $trest = $tnum;
+                        if ($tsec ne '') {
+                            $area .= '/'.$tsec;
+                            $trest .= '/'.$tsec;
+                        }
+                        $spec = $trole.'.'.$area;
+                        if ($trole =~ /^cr/) {
+                            &Apache::lonnet::custom_roleprivs(\%allroles,$trole,
+                                                              $tdom,$spec,$trest,$area);
+                        } else {
+                            &Apache::lonnet::standard_roleprivs(\%allroles,$trole,
+                                                                $tdom,$spec,$trest,$area);
+                        }
+                    }
+                    my ($author,$adv) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
+                    if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) {
+                        if ($1) {
+                            $no_userblock = 1;
+                            last;
+                        }
                     }
                 }
             } else {
@@ -4138,46 +4208,139 @@ sub blockcheck {
         # Retrieve blocking times and identity of locker for course
         # of specified user, unless user has 'evb' privilege.
         
-        my ($start,$end)=&get_blocks($setters,$activity,$cdom,$cnum);
+        my ($start,$end,$trigger) = 
+            &get_blocks($setters,$activity,$cdom,$cnum,$url);
         if (($start != 0) && 
             (($startblock == 0) || ($startblock > $start))) {
             $startblock = $start;
+            if ($trigger ne '') {
+                $triggerblock = $trigger;
+            }
         }
         if (($end != 0)  &&
             (($endblock == 0) || ($endblock < $end))) {
             $endblock = $end;
+            if ($trigger ne '') {
+                $triggerblock = $trigger;
+            }
         }
     }
-    return ($startblock,$endblock);
+    return ($startblock,$endblock,$triggerblock);
 }
 
 sub get_blocks {
-    my ($setters,$activity,$cdom,$cnum) = @_;
+    my ($setters,$activity,$cdom,$cnum,$url) = @_;
     my $startblock = 0;
     my $endblock = 0;
+    my $triggerblock = '';
     my $course = $cdom.'_'.$cnum;
     $setters->{$course} = {};
     $setters->{$course}{'staff'} = [];
     $setters->{$course}{'times'} = [];
-    my %records = &Apache::lonnet::dump('comm_block',$cdom,$cnum);
-    foreach my $record (keys(%records)) {
-        my ($start,$end) = ($record =~ m/^(\d+)____(\d+)$/);
-        if ($start <= time && $end >= time) {
-            my ($staff_name,$staff_dom,$title,$blocks) =
-                &parse_block_record($records{$record});
-            if ($blocks->{$activity} eq 'on') {
-                push(@{$$setters{$course}{'staff'}},[$staff_name,$staff_dom]);
-                push(@{$$setters{$course}{'times'}}, [$start,$end]);
-                if ( ($startblock == 0) || ($startblock > $start) ) {
-                    $startblock = $start;
+    $setters->{$course}{'triggers'} = [];
+    my (@blockers,%triggered);
+    my $now = time;
+    my %commblocks = &Apache::lonnet::get_comm_blocks($cdom,$cnum);
+    if ($activity eq 'docs') {
+        @blockers = &Apache::lonnet::has_comm_blocking('bre',undef,$url,\%commblocks);
+        foreach my $block (@blockers) {
+            if ($block =~ /^firstaccess____(.+)$/) {
+                my $item = $1;
+                my $type = 'map';
+                my $timersymb = $item;
+                if ($item eq 'course') {
+                    $type = 'course';
+                } elsif ($item =~ /___\d+___/) {
+                    $type = 'resource';
+                } else {
+                    $timersymb = &Apache::lonnet::symbread($item);
+                }
+                my $start = $env{'course.'.$cdom.'_'.$cnum.'.firstaccess.'.$timersymb};
+                my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb};
+                $triggered{$block} = {
+                                       start => $start,
+                                       end   => $end,
+                                       type  => $type,
+                                     };
+            }
+        }
+    } else {
+        foreach my $block (keys(%commblocks)) {
+            if ($block =~ m/^(\d+)____(\d+)$/) { 
+                my ($start,$end) = ($1,$2);
+                if ($start <= time && $end >= time) {
+                    if (ref($commblocks{$block}) eq 'HASH') {
+                        if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
+                            if ($commblocks{$block}{'blocks'}{$activity} eq 'on') {
+                                unless(grep(/^\Q$block\E$/,@blockers)) {
+                                    push(@blockers,$block);
+                                }
+                            }
+                        }
+                    }
+                }
+            } elsif ($block =~ /^firstaccess____(.+)$/) {
+                my $item = $1;
+                my $timersymb = $item; 
+                my $type = 'map';
+                if ($item eq 'course') {
+                    $type = 'course';
+                } elsif ($item =~ /___\d+___/) {
+                    $type = 'resource';
+                } else {
+                    $timersymb = &Apache::lonnet::symbread($item);
                 }
-                if ( ($endblock == 0) || ($endblock < $end) ) {
-                    $endblock = $end;
+                my $start = $env{'course.'.$cdom.'_'.$cnum.'.firstaccess.'.$timersymb};
+                my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb}; 
+                if ($start && $end) {
+                    if (($start <= time) && ($end >= time)) {
+                        unless (grep(/^\Q$block\E$/,@blockers)) {
+                            push(@blockers,$block);
+                            $triggered{$block} = {
+                                                   start => $start,
+                                                   end   => $end,
+                                                   type  => $type,
+                                                 };
+                        }
+                    }
+                }
+            }
+        }
+    }
+    foreach my $blocker (@blockers) {
+        my ($staff_name,$staff_dom,$title,$blocks) =
+            &parse_block_record($commblocks{$blocker});
+        push(@{$$setters{$course}{'staff'}},[$staff_name,$staff_dom]);
+        my ($start,$end,$triggertype);
+        if ($blocker =~ m/^(\d+)____(\d+)$/) {
+            ($start,$end) = ($1,$2);
+        } elsif (ref($triggered{$blocker}) eq 'HASH') {
+            $start = $triggered{$blocker}{'start'};
+            $end = $triggered{$blocker}{'end'};
+            $triggertype = $triggered{$blocker}{'type'};
+        }
+        if ($start) {
+            push(@{$$setters{$course}{'times'}}, [$start,$end]);
+            if ($triggertype) {
+                push(@{$$setters{$course}{'triggers'}},$triggertype);
+            } else {
+                push(@{$$setters{$course}{'triggers'}},0);
+            }
+            if ( ($startblock == 0) || ($startblock > $start) ) {
+                $startblock = $start;
+                if ($triggertype) {
+                    $triggerblock = $blocker;
                 }
             }
+            if ( ($endblock == 0) || ($endblock < $end) ) {
+               $endblock = $end;
+               if ($triggertype) {
+                   $triggerblock = $blocker;
+               }
+            }
         }
     }
-    return ($startblock,$endblock);
+    return ($startblock,$endblock,$triggerblock);
 }
 
 sub parse_block_record {
@@ -4201,39 +4364,50 @@ sub parse_block_record {
 }
 
 sub blocking_status {
-  my ($activity,$uname,$udom) = @_;
-  my %setters;
+    my ($activity,$uname,$udom,$url) = @_;
+    my %setters;
 
-  # check for active blocking
-  my ($startblock,$endblock)=&blockcheck(\%setters,$activity,$uname,$udom);
-
-  my $blocked = $startblock && $endblock ? 1 : 0;
-
-  # caller just wants to know whether a block is active
-  if (!wantarray) { return $blocked; }
-
-  # build a link to a popup window containing the details
-  my $querystring  = "?activity=$activity";
-  # $uname and $udom decide whose portfolio the user is trying to look at
-     $querystring .= "&amp;udom=$udom"      if $udom;
-     $querystring .= "&amp;uname=$uname"    if $uname;
-
-  my $output .= <<'END_MYBLOCK';
-    function openWindow(url, wdwName, w, h, toolbar,scrollbar) {
-        var options = "width=" + w + ",height=" + h + ",";
-        options += "resizable=yes,scrollbars="+scrollbar+",status=no,";
-        options += "menubar=no,toolbar="+toolbar+",location=no,directories=no";
-        var newWin = window.open(url, wdwName, options);
-        newWin.focus();
-    }
+# check for active blocking
+    my ($startblock,$endblock,$triggerblock) = 
+        &blockcheck(\%setters,$activity,$uname,$udom,$url);
+    my $blocked = 0;
+    if ($startblock && $endblock) {
+        $blocked = 1;
+    }
+
+# caller just wants to know whether a block is active
+    if (!wantarray) { return $blocked; }
+
+# build a link to a popup window containing the details
+    my $querystring  = "?activity=$activity";
+# $uname and $udom decide whose portfolio the user is trying to look at
+    if ($activity eq 'port') {
+        $querystring .= "&amp;udom=$udom"      if $udom;
+        $querystring .= "&amp;uname=$uname"    if $uname;
+    } elsif ($activity eq 'docs') {
+        $querystring .= '&amp;url='.&HTML::Entities::encode($url,'&"');
+    }
+
+    my $output .= <<'END_MYBLOCK';
+function openWindow(url, wdwName, w, h, toolbar,scrollbar) {
+    var options = "width=" + w + ",height=" + h + ",";
+    options += "resizable=yes,scrollbars="+scrollbar+",status=no,";
+    options += "menubar=no,toolbar="+toolbar+",location=no,directories=no";
+    var newWin = window.open(url, wdwName, options);
+    newWin.focus();
+}
 END_MYBLOCK
 
-  $output = Apache::lonhtmlcommon::scripttag($output);
+    $output = Apache::lonhtmlcommon::scripttag($output);
   
-  my $popupUrl = "/adm/blockingstatus/$querystring";
-  my $text = mt('Communication Blocked');
-
-  $output .= <<"END_BLOCK";
+    my $popupUrl = "/adm/blockingstatus/$querystring";
+    my $text = &mt('Communication Blocked');
+    if ($activity eq 'docs') {
+        $text = &mt('Content Access Blocked');
+    } elsif ($activity eq 'printout') {
+        $text = &mt('Printing Blocked');
+    }
+    $output .= <<"END_BLOCK";
 <div class='LC_comblock'>
   <a onclick='openWindow("$popupUrl","Blocking Table",600,300,"no","no");return false;' href='/adm/blockingstatus/$querystring'
   title='$text'>
@@ -4244,7 +4418,7 @@ END_MYBLOCK
 
 END_BLOCK
 
-  return ($blocked, $output);
+    return ($blocked, $output);
 }
 
 ###############################################
@@ -4761,7 +4935,8 @@ sub bodytag {
     if ($public) {
 	undef($role);
     } else {
-	$name = &aboutmewrapper($name,$env{'user.name'},$env{'user.domain'});
+	$name = &aboutmewrapper($name,$env{'user.name'},$env{'user.domain'},
+                                undef,'LC_menubuttons_link');
     }
     
     my $titleinfo = '<h1>'.$title.'</h1>';
@@ -4838,7 +5013,7 @@ sub bodytag {
 sub dc_courseid_toggle {
     my ($dc_info) = @_;
     return ' <span id="dccidtext" class="LC_cusr_subheading LC_nobreak">'.
-           '<a href="javascript:showCourseID();">'.
+           '<a href="javascript:showCourseID();" class="LC_menubuttons_link">'.
            &mt('(More ...)').'</a></span>'.
            '<div id="dccid" class="LC_dccid">'.$dc_info.'</div>';
 }
@@ -4943,7 +5118,7 @@ sub standard_css {
     my $mono                 = 'monospace';
     my $data_table_head      = $sidebg;
     my $data_table_light     = '#FAFAFA';
-    my $data_table_dark      = '#F0F0F0';
+    my $data_table_dark      = '#E0E0E0';
     my $data_table_darker    = '#CCCCCC';
     my $data_table_highlight = '#FFFF00';
     my $mail_new             = '#FFBB77';
@@ -4983,7 +5158,6 @@ body {
 a:focus,
 a:focus img {
   color: red;
-  background: yellow;
 }
 
 form, .inline {
@@ -5078,35 +5252,36 @@ div.LC_confirm_box .LC_success img {
 }
 
 .LC_discussion {
-  background: $tabbg;
+  background: $data_table_dark;
   border: 1px solid black;
   margin: 2px;
 }
 
-.LC_disc_action_links_bar {
-  background: $tabbg;
-  border: none;
-  margin: 4px;
-}
-
 .LC_disc_action_left {
+  background: $sidebg;
   text-align: left;
+  padding: 4px;
+  margin: 2px;
 }
 
 .LC_disc_action_right {
+  background: $sidebg;
   text-align: right;
+  padding: 4px;
+  margin: 2px;
 }
 
 .LC_disc_new_item {
   background: white;
   border: 2px solid red;
-  margin: 2px;
+  margin: 4px;
+  padding: 4px;
 }
 
 .LC_disc_old_item {
   background: white;
-  border: 1px solid black;
-  margin: 2px;
+  margin: 4px;
+  padding: 4px;
 }
 
 table.LC_pastsubmission {
@@ -5228,7 +5403,7 @@ td.LC_table_cell_checkbox {
   vertical-align: middle;
 }
 
-li.LC_menubuttons_inline_text img,a {
+li.LC_menubuttons_inline_text img {
   cursor:pointer;
   text-decoration: none;
 }
@@ -6030,6 +6205,7 @@ div.LC_edit_problem_footer {
   font-weight: normal;
   font-size:  medium;
   margin: 2px;
+  background-color: $sidebg;
 }
 
 div.LC_edit_problem_header,
@@ -6046,6 +6222,7 @@ div.LC_edit_problem_header_title {
   font-size: larger;
   background: $tabbg;
   padding: 3px;
+  margin: 0 0 5px 0;
 }
 
 table.LC_edit_problem_header_title {
@@ -6083,7 +6260,6 @@ div.LC_createcourse {
   display:none;
 }
 
-a:hover,
 ol.LC_primary_menu a:hover,
 ol#LC_MenuBreadcrumbs a:hover,
 ol#LC_PathBreadcrumbs a:hover,
@@ -6317,6 +6493,12 @@ ul.LC_TabContent li.active a {
   background:#FFFFFF;
   outline: none;
 }
+
+ul.LC_TabContent li.goback {
+  float: left;
+  border-left: none;
+}
+
 #maincoursedoc {
   clear:both;
 }
@@ -6566,6 +6748,10 @@ a#LC_content_toolbar_changefolder_toggle
   background-image:url(/res/adm/pages/open-all-folders.gif);
 }
 
+a#LC_content_toolbar_edittoplevel {
+  background-image:url(/res/adm/pages/edittoplevel.gif);
+}
+
 ul#LC_toolbar li a:hover {
   background-position: bottom center;
 }
@@ -6729,6 +6915,8 @@ sub headtag {
 	'<head>'.
 	&font_settings();
 
+    my $inhibitprint = &print_suppression();
+
     if (!$args->{'frameset'}) {
 	$result .= &Apache::lonhtmlcommon::htmlareaheaders();
     }
@@ -6740,6 +6928,22 @@ sub headtag {
 	&& !$args->{'frameset'}) {
 	$result .= &help_menu_js();
         $result.=&modal_window();
+        $result.=&togglebox_script();
+        $result.=&wishlist_window();
+        $result.=&LCprogressbarUpdate_script();
+    } else {
+        if ($args->{'add_modal'}) {
+           $result.=&modal_window();
+        }
+        if ($args->{'add_wishlist'}) {
+           $result.=&wishlist_window();
+        }
+        if ($args->{'add_togglebox'}) {
+           $result.=&togglebox_script();
+        }
+        if ($args->{'add_progressbar'}) {
+           $result.=&LCprogressbarUpdate_script();
+        }
     }
     if (ref($args->{'redirect'})) {
 	my ($time,$url,$inhibit_continue) = @{$args->{'redirect'}};
@@ -6758,6 +6962,7 @@ ADDMETA
     if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
     $result .= '<title> LON-CAPA '.$title.'</title>'
 	.'<link rel="stylesheet" type="text/css" href="'.$url.'" />'
+        .$inhibitprint
 	.$head_extra;
     return $result.'</head>';
 }
@@ -6783,6 +6988,82 @@ sub font_settings {
 
 =pod
 
+=item * &print_suppression()
+
+In course context returns css which causes the body to be blank when media="print",
+if printout generation is unavailable for the current resource.
+
+This could be because:
+
+(a) printstartdate is in the future
+
+(b) printenddate is in the past
+
+(c) there is an active exam block with "printout"
+functionality blocked
+
+Users with pav, pfo or evb privileges are exempt.
+
+Inputs: none
+
+=cut
+
+
+sub print_suppression {
+    my $noprint;
+    if ($env{'request.course.id'}) {
+        my $scope = $env{'request.course.id'};
+        if ((&Apache::lonnet::allowed('pav',$scope)) ||
+            (&Apache::lonnet::allowed('pfo',$scope))) {
+            return;
+        }
+        if ($env{'request.course.sec'} ne '') {
+            $scope .= "/$env{'request.course.sec'}";
+            if ((&Apache::lonnet::allowed('pav',$scope)) ||
+                (&Apache::lonnet::allowed('pfo',$scope))) {
+                return;
+            }
+        }
+        my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+        my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+        my $blocked = &blocking_status('printout',$cnum,$cdom);
+        if ($blocked) {
+            my $checkrole = "cm./$cdom/$cnum";
+            if ($env{'request.course.sec'} ne '') {
+                $checkrole .= "/$env{'request.course.sec'}";
+            }
+            unless ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) &&
+                    ($env{'request.role'} !~ m{^st\./$cdom/$cnum})) {
+                $noprint = 1;
+            }
+        }
+        unless ($noprint) {
+            my $symb = &Apache::lonnet::symbread();
+            if ($symb ne '') {
+                my $navmap = Apache::lonnavmaps::navmap->new();
+                if (ref($navmap)) {
+                    my $res = $navmap->getBySymb($symb);
+                    if (ref($res)) {
+                        if (!$res->resprintable()) {
+                            $noprint = 1;
+                        }
+                    }
+                }
+            }
+        }
+        if ($noprint) {
+            return <<"ENDSTYLE";
+<style type="text/css" media="print">
+    body { display:none }
+</style>
+ENDSTYLE
+        }
+    }
+    return;
+}
+
+=pod
+
 =item * &xml_begin()
 
 Returns the needed doctype and <html>
@@ -6954,9 +7235,31 @@ sub end_page {
     return $result;
 }
 
+sub wishlist_window {
+    return(<<'ENDWISHLIST');
+<script type="text/javascript">
+// <![CDATA[
+// <!-- BEGIN LON-CAPA Internal
+function set_wishlistlink(title, path) {
+    if (!title) {
+        title = document.title;
+        title = title.replace(/^LON-CAPA /,'');
+    }
+    if (!path) {
+        path = location.pathname;
+    }
+    Win = window.open('/adm/wishlist?mode=newLink&setTitle='+title+'&setPath='+path,
+                      'wishlistNewLink','width=560,height=350,scrollbars=0');
+}
+// END LON-CAPA Internal -->
+// ]]>
+</script>
+ENDWISHLIST
+}
+
 sub modal_window {
     return(<<'ENDMODAL');
-<script type="text/javascript" lang="javascript">
+<script type="text/javascript">
 // <![CDATA[
 // <!-- BEGIN LON-CAPA Internal
 var modalWindow = {
@@ -7000,18 +7303,18 @@ ENDMODAL
 }
 
 sub modal_link {
-    my ($link,$linktext,$width,$height,$target,$scrolling)=@_;
+    my ($link,$linktext,$width,$height,$target,$scrolling,$title)=@_;
     unless ($width) { $width=480; }
     unless ($height) { $height=400; }
     unless ($scrolling) { $scrolling='yes'; }
-    return '<a href="'.$link.'" target="'.$target.'" onclick="openMyModal(\''.$link.'\','.$width.','.$height.',\''.$scrolling.'\'); return false;">'.
+    return '<a href="'.$link.'" target="'.$target.'" title="'.$title.'" onclick="openMyModal(\''.$link.'\','.$width.','.$height.',\''.$scrolling.'\'); return false;">'.
            $linktext.'</a>';
 }
 
 sub modal_adhoc_script {
     my ($funcname,$width,$height,$content)=@_;
     return (<<ENDADHOC);
-<script type="text/javascript" lang="javascript">
+<script type="text/javascript">
 // <![CDATA[
         var $funcname = function()
         {
@@ -7026,6 +7329,187 @@ sub modal_adhoc_script {
 ENDADHOC
 }
 
+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.
+                 &end_scrollbox().
+               &end_page()
+             );
+    return &modal_adhoc_script($funcname,$width,$height,$content);
+}
+
+sub modal_adhoc_window {
+    my ($funcname,$width,$height,$content,$linktext)=@_;
+    return &modal_adhoc_inner($funcname,$width,$height,$content).
+           "<a href=\"javascript:$funcname();void(0);\">".$linktext."</a>";
+}
+
+sub modal_adhoc_launch {
+    my ($funcname,$width,$height,$content)=@_;
+    return &modal_adhoc_inner($funcname,$width,$height,$content).(<<ENDLAUNCH);
+<script type="text/javascript">
+// <![CDATA[
+$funcname();
+// ]]>
+</script>
+ENDLAUNCH
+}
+
+sub modal_adhoc_close {
+    return (<<ENDCLOSE);
+<script type="text/javascript">
+// <![CDATA[
+modalWindow.close();
+// ]]>
+</script>
+ENDCLOSE
+}
+
+sub togglebox_script {
+   return(<<ENDTOGGLE);
+<script type="text/javascript"> 
+// <![CDATA[
+function LCtoggleDisplay(id,hidetext,showtext) {
+   link = document.getElementById(id + "link").childNodes[0];
+   with (document.getElementById(id).style) {
+      if (display == "none" ) {
+          display = "inline";
+          link.nodeValue = hidetext;
+        } else {
+          display = "none";
+          link.nodeValue = showtext;
+       }
+   }
+}
+// ]]>
+</script>
+ENDTOGGLE
+}
+
+sub start_togglebox {
+    my ($id,$heading,$headerbg,$hidetext,$showtext)=@_;
+    unless ($heading) { $heading=''; } else { $heading.=' '; }
+    unless ($showtext) { $showtext=&mt('show'); }
+    unless ($hidetext) { $hidetext=&mt('hide'); }
+    unless ($headerbg) { $headerbg='#FFFFFF'; }
+    return &start_data_table().
+           &start_data_table_header_row().
+           '<td bgcolor="'.$headerbg.'">'.$heading.
+           '[<a id="'.$id.'link" href="javascript:LCtoggleDisplay(\''.$id.'\',\''.$hidetext.'\',\''.
+           $showtext.'\')">'.$showtext.'</a>]</td>'.
+           &end_data_table_header_row().
+           '<tr id="'.$id.'" style="display:none""><td>';
+}
+
+sub end_togglebox {
+    return '</td></tr>'.&end_data_table();
+}
+
+sub LCprogressbar_script {
+   my ($id)=@_;
+   return(<<ENDPROGRESS);
+<script type="text/javascript">
+// <![CDATA[
+\$('#progressbar$id').progressbar({
+  value: 0,
+  change: function(event, ui) {
+    var newVal = \$(this).progressbar('option', 'value');
+    \$('.pblabel', this).text(LCprogressTxt);
+  }
+});
+// ]]>
+</script>
+ENDPROGRESS
+}
+
+sub LCprogressbarUpdate_script {
+   return(<<ENDPROGRESSUPDATE);
+<style type="text/css">
+.ui-progressbar { position:relative; }
+.pblabel { position: absolute; width: 100%; text-align: center; line-height: 1.9em; }
+</style>
+<script type="text/javascript">
+// <![CDATA[
+var LCprogressTxt='---';
+
+function LCupdateProgress(percent,progresstext,id) {
+   LCprogressTxt=progresstext;
+   \$('#progressbar'+id).progressbar('value',percent);
+}
+// ]]>
+</script>
+ENDPROGRESSUPDATE
+}
+
+my $LClastpercent;
+my $LCidcnt;
+my $LCcurrentid;
+
+sub LCprogressbar {
+    my ($r)=(@_);
+    $LClastpercent=0;
+    $LCidcnt++;
+    $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));
+}
+
+sub LCprogressbarUpdate {
+    my ($r,$val,$text)=@_;
+    unless ($val) { 
+       if ($LClastpercent) {
+           $val=$LClastpercent;
+       } else {
+           $val=0;
+       }
+    }
+    if ($val<0) { $val=0; }
+    if ($val>100) { $val=0; }
+    $LClastpercent=$val;
+    unless ($text) { $text=$val.'%'; }
+    $text=&js_ready($text);
+    &r_print($r,<<ENDUPDATE);
+<script type="text/javascript">
+// <![CDATA[
+LCupdateProgress($val,'$text','$LCcurrentid');
+// ]]>
+</script>
+ENDUPDATE
+}
+
+sub LCprogressbarClose {
+    my ($r)=@_;
+    $LClastpercent=0;
+    &r_print($r,<<ENDCLOSE);
+<script type="text/javascript">
+// <![CDATA[
+\$("#progressbar$LCcurrentid").hide('slow'); 
+// ]]>
+</script>
+ENDCLOSE
+}
+
+sub r_print {
+    my ($r,$to_print)=@_;
+    if ($r) {
+      $r->print($to_print);
+      $r->rflush();
+    } else {
+      print($to_print);
+    }
+}
+
 sub html_encode {
     my ($result) = @_;
 
@@ -7033,6 +7517,7 @@ sub html_encode {
     
     return $result;
 }
+
 sub js_ready {
     my ($result) = @_;
 
@@ -7084,7 +7569,7 @@ sub start_scrollbox {
 }
 
 sub end_scrollbox {
-    return '</td></tr></table>';
+    return '</div></td></tr></table>';
 }
 
 sub simple_error_page {
@@ -7306,8 +7791,7 @@ role status: active, previous or future.
 
 sub check_user_status {
     my ($udom,$uname,$cdom,$crs,$role,$sec) = @_;
-    my $extra = &Apache::lonnet::freeze_escape({'skipcheck' => 1});
-    my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname,'.',undef,$extra);
+    my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname);
     my @uroles = keys %userinfo;
     my $srchstr;
     my $active_chk = 'none';
@@ -8363,7 +8847,8 @@ sub get_standard_codeitems {
 
 =item * sorted_slots()
 
-Sorts an array of slot names in order of slot start time (earliest first). 
+Sorts an array of slot names in order of an optional sort key,
+default sort is by slot start time (earliest first). 
 
 Inputs:
 
@@ -8373,15 +8858,16 @@ slotsarr  - Reference to array of unsort
 
 slots     - Reference to hash of hash, where outer hash keys are slot names.
 
+sortkey   - Name of key in inner hash to be sorted on (e.g., starttime).
+
 =back
 
 Returns:
 
 =over 4
 
-sorted   - An array of slot names sorted by the start time of the slot.
-
-=back
+sorted   - An array of slot names sorted by a specified sort key 
+           (default sort key is start time of the slot).
 
 =back
 
@@ -8389,13 +8875,16 @@ sorted   - An array of slot names sorted
 
 
 sub sorted_slots {
-    my ($slotsarr,$slots) = @_;
+    my ($slotsarr,$slots,$sortkey) = @_;
+    if ($sortkey eq '') {
+        $sortkey = 'starttime';
+    }
     my @sorted;
     if ((ref($slotsarr) eq 'ARRAY') && (ref($slots) eq 'HASH')) {
         @sorted =
             sort {
                      if (ref($slots->{$a}) && ref($slots->{$b})) {
-                         return $slots->{$a}{'starttime'} <=> $slots->{$b}{'starttime'}
+                         return $slots->{$a}{$sortkey} <=> $slots->{$b}{$sortkey}
                      }
                      if (ref($slots->{$a})) { return -1;}
                      if (ref($slots->{$b})) { return 1;}
@@ -8405,9 +8894,136 @@ sub sorted_slots {
     return @sorted;
 }
 
+=pod
+
+=item * get_future_slots()
+
+Inputs:
+
+=over 4
+
+cnum - course number
+
+cdom - course domain
+
+now - current UNIX time
+
+symb - optional symb
+
+=back
+
+Returns:
+
+=over 4
+
+sorted_reservable - ref to array of student_schedulable slots currently 
+                    reservable, ordered by end date of reservation period.
+
+reservable_now - ref to hash of student_schedulable slots currently
+                 reservable.
+
+    Keys in inner hash are:
+    (a) symb: either blank or symb to which slot use is restricted.
+    (b) endreserve: end date of reservation period. 
+
+sorted_future - ref to array of student_schedulable slots reservable in
+                the future, ordered by start date of reservation period.
+
+future_reservable - ref to hash of student_schedulable slots reservable
+                    in the future.
+
+    Keys in inner hash are:
+    (a) symb: either blank or symb to which slot use is restricted.
+    (b) startreserve:  start date of reservation period.
+
+=back
+
+=cut
+
+sub get_future_slots {
+    my ($cnum,$cdom,$now,$symb) = @_;
+    my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future);
+    my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom);
+    foreach my $slot (keys(%slots)) {
+        next unless($slots{$slot}->{'type'} eq 'schedulable_student');
+        if ($symb) {
+            next if (($slots{$slot}->{'symb'} ne '') && 
+                     ($slots{$slot}->{'symb'} ne $symb));
+        }
+        if (($slots{$slot}->{'starttime'} > $now) &&
+            ($slots{$slot}->{'endtime'} > $now)) {
+            if (($slots{$slot}->{'allowedsections'}) || ($slots{$slot}->{'allowedusers'})) {
+                my $userallowed = 0;
+                if ($slots{$slot}->{'allowedsections'}) {
+                    my @allowed_sec = split(',',$slots{$slot}->{'allowedsections'});
+                    if (!defined($env{'request.role.sec'})
+                        && grep(/^No section assigned$/,@allowed_sec)) {
+                        $userallowed=1;
+                    } else {
+                        if (grep(/^\Q$env{'request.role.sec'}\E$/,@allowed_sec)) {
+                            $userallowed=1;
+                        }
+                    }
+                    unless ($userallowed) {
+                        if (defined($env{'request.course.groups'})) {
+                            my @groups = split(/:/,$env{'request.course.groups'});
+                            foreach my $group (@groups) {
+                                if (grep(/^\Q$group\E$/,@allowed_sec)) {
+                                    $userallowed=1;
+                                    last;
+                                }
+                            }
+                        }
+                    }
+                }
+                if ($slots{$slot}->{'allowedusers'}) {
+                    my @allowed_users = split(',',$slots{$slot}->{'allowedusers'});
+                    my $user = $env{'user.name'}.':'.$env{'user.domain'};
+                    if (grep(/^\Q$user\E$/,@allowed_users)) {
+                        $userallowed = 1;
+                    }
+                }
+                next unless($userallowed);
+            }
+            my $startreserve = $slots{$slot}->{'startreserve'};
+            my $endreserve = $slots{$slot}->{'endreserve'};
+            my $symb = $slots{$slot}->{'symb'};
+            if (($startreserve < $now) &&
+                (!$endreserve || $endreserve > $now)) {
+                my $lastres = $endreserve;
+                if (!$lastres) {
+                    $lastres = $slots{$slot}->{'starttime'};
+                }
+                $reservable_now{$slot} = {
+                                           symb       => $symb,
+                                           endreserve => $lastres
+                                         };
+            } elsif (($startreserve > $now) &&
+                     (!$endreserve || $endreserve > $startreserve)) {
+                $future_reservable{$slot} = {
+                                              symb         => $symb,
+                                              startreserve => $startreserve
+                                            };
+            }
+        }
+    }
+    my @unsorted_reservable = keys(%reservable_now);
+    if (@unsorted_reservable > 0) {
+        @sorted_reservable = 
+            &sorted_slots(\@unsorted_reservable,\%reservable_now,'endreserve');
+    }
+    my @unsorted_future = keys(%future_reservable);
+    if (@unsorted_future > 0) {
+        @sorted_future =
+            &sorted_slots(\@unsorted_future,\%future_reservable,'startreserve');
+    }
+    return (\@sorted_reservable,\%reservable_now,\@sorted_future,\%future_reservable);
+}
 
 =pod
 
+=back
+
 =head1 HTTP Helpers
 
 =over 4
@@ -8546,13 +9162,20 @@ sub get_env_multiple {
 
 sub ask_for_embedded_content {
     my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
-    my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges);
-    my $num = 0;
+    my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges,
+        %currsubfile,%unused);
+    my $counter = 0;
+    my $numnew = 0;
     my $numremref = 0;
     my $numinvalid = 0;
     my $numpathchg = 0;
     my $numexisting = 0;
-    my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath);
+    my $numunused = 0;
+    my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath,$cdom,$cnum,
+        $fileloc,$filename,$delete_output,$modify_output,$title,$symb,$path);
+    my $heading = &mt('Upload embedded files');
+    my $buttontext = &mt('Upload');
+
     if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
         my $current_path='/';
         if ($env{'form.currentpath'}) {
@@ -8580,8 +9203,24 @@ sub ask_for_embedded_content {
         }
     } elsif ($actionurl eq '/adm/coursedocs') {
         if (ref($args) eq 'HASH') {
-           $url = $args->{'docs_url'};
-           $toplevel = $url;
+            $url = $args->{'docs_url'};
+            $toplevel = $url;
+        }
+    } elsif ($actionurl eq '/adm/dependencies') {
+        if ($env{'request.course.id'} ne '') {
+            $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+            $cnum =  $env{'course.'.$env{'request.course.id'}.'.num'};
+            if (ref($args) eq 'HASH') {
+                $url = $args->{'docs_url'};
+                $title = $args->{'docs_title'};
+                $toplevel = "/$url";
+                ($path) =  
+                    ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
+                $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+                $fileloc =~ s{^/}{};
+                ($filename) = ($fileloc =~ m{.+/([^/]+)$});
+                $heading = &mt('Status of dependencies in [_1]',"$title ($filename)");
+            }
         }
     }
     my $now = time();
@@ -8620,25 +9259,44 @@ sub ask_for_embedded_content {
             }
         }
     }
+    my $dirptr = 16384;
     foreach my $path (keys(%subdependencies)) {
-        my %currsubfile;
+        $currsubfile{$path} = {};
         if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) { 
             my ($sublistref,$listerror) =
                 &Apache::lonnet::dirlist($url.$path,$udom,$uname,$getpropath);
             if (ref($sublistref) eq 'ARRAY') {
                 foreach my $line (@{$sublistref}) {
                     my ($file_name,$rest) = split(/\&/,$line,2);
-                    $currsubfile{$file_name} = 1;
+                    $currsubfile{$path}{$file_name} = 1;
                 }
             }
         } elsif (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank')) {
             if (opendir(my $dir,$url.'/'.$path)) {
                 my @subdir_list = grep(!/^\./,readdir($dir));
-                map {$currsubfile{$_} = 1;} @subdir_list;
+                map {$currsubfile{$path}{$_} = 1;} @subdir_list;
+            }
+        } elsif ($actionurl eq '/adm/dependencies') {
+            if ($env{'request.course.id'} ne '') {
+                my ($dir) = ($fileloc =~ m{^(.+/)[^/]+$});
+                if ($dir ne '') {
+                    my ($sublistref,$listerror) =
+                        &Apache::lonnet::dirlist($dir.$path,$cdom,$cnum,$getpropath,undef,'/');
+                    if (ref($sublistref) eq 'ARRAY') {
+                        foreach my $line (@{$sublistref}) {
+                            my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef,$size,
+                                undef,$mtime)=split(/\&/,$line,12);
+                            unless (($testdir&$dirptr) ||
+                                    ($file_name =~ /^\.\.?$/)) {
+                                $currsubfile{$path}{$file_name} = [$size,$mtime];
+                            }
+                        }
+                    }
+                }
             }
         }
         foreach my $file (keys(%{$subdependencies{$path}})) {
-            if ($currsubfile{$file}) {
+            if (exists($currsubfile{$path}{$file})) {
                 my $item = $path.'/'.$file;
                 unless ($mapping{$item} eq $item) {
                     $pathchanges{$item} = 1;
@@ -8649,6 +9307,17 @@ sub ask_for_embedded_content {
                 $newfiles{$path.'/'.$file} = 1;
             }
         }
+        if ($actionurl eq '/adm/dependencies') {
+            foreach my $path (keys(%currsubfile)) {
+                if (ref($currsubfile{$path}) eq 'HASH') {
+                    foreach my $file (keys(%{$currsubfile{$path}})) {
+                         unless ($subdependencies{$path}{$file}) {
+                             $unused{$path.'/'.$file} = 1; 
+                         }
+                    }
+                }
+            }
+        }
     }
     my %currfile;
     if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
@@ -8665,9 +9334,27 @@ sub ask_for_embedded_content {
             my @dir_list = grep(!/^\./,readdir($dir));
             map {$currfile{$_} = 1;} @dir_list;
         }
+    } elsif ($actionurl eq '/adm/dependencies') {
+        if ($env{'request.course.id'} ne '') {
+            my ($dir) = ($fileloc =~ m{^(.+/)[^/]+$});
+            if ($dir ne '') {
+                my ($dirlistref,$listerror) =
+                    &Apache::lonnet::dirlist($dir,$cdom,$cnum,$getpropath,undef,'/');
+                if (ref($dirlistref) eq 'ARRAY') {
+                    foreach my $line (@{$dirlistref}) {
+                        my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef,
+                            $size,undef,$mtime)=split(/\&/,$line,12);
+                        unless (($testdir&$dirptr) ||
+                                ($file_name =~ /^\.\.?$/)) {
+                            $currfile{$file_name} = [$size,$mtime];
+                        }
+                    }
+                }
+            }
+        }
     }
     foreach my $file (keys(%dependencies)) {
-        if ($currfile{$file}) {
+        if (exists($currfile{$file})) {
             unless ($mapping{$file} eq $file) {
                 $pathchanges{$file} = 1;
             }
@@ -8677,15 +9364,25 @@ sub ask_for_embedded_content {
             $newfiles{$file} = 1;
         }
     }
+    foreach my $file (keys(%currfile)) {
+        unless (($file eq $filename) ||
+                ($file eq $filename.'.bak') ||
+                ($dependencies{$file})) {
+            $unused{$file} = 1;
+        }
+    }
     foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) {
+        if ($actionurl eq '/adm/dependencies') {
+            next if ($embed_file =~ m{^\w+://});
+        }
         $upload_output .= &start_data_table_row().
-                          '<td><span class="LC_filename">'.$embed_file.'</span>';
+                          '<td><img src="'.&icon($embed_file).'" />&nbsp;'.
+                          '<span class="LC_filename">'.$embed_file.'</span>';
         unless ($mapping{$embed_file} eq $embed_file) {
             $upload_output .= '<br /><span class="LC_info" style="font-size:smaller;">'.&mt('changed from: [_1]',$mapping{$embed_file}).'</span>';
         }
         $upload_output .= '</td><td>';
-        if ($args->{'ignore_remote_references'}
-            && $embed_file =~ m{^\w+://}) {
+        if ($args->{'ignore_remote_references'} && $embed_file =~ m{^\w+://}) { 
             $upload_output.='<span class="LC_warning">'.&mt("URL points to other server.").'</span>';
             $numremref++;
         } elsif ($args->{'error_on_invalid_names'}
@@ -8694,24 +9391,84 @@ sub ask_for_embedded_content {
             $upload_output.='<span class="LC_warning">'.&mt('Invalid characters').'</span>';
             $numinvalid++;
         } else {
-            $upload_output .= &embedded_file_element('upload_embedded',$num,
+            $upload_output .= &embedded_file_element('upload_embedded',$counter,
                                                      $embed_file,\%mapping,
-                                                     $allfiles,$codebase);
-            $num++;
+                                                     $allfiles,$codebase,'upload');
+            $counter ++;
+            $numnew ++;
         }
         $upload_output .= '</td>'.&Apache::loncommon::end_data_table_row()."\n";
     }
     foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%existing)) {
-        $upload_output .= &start_data_table_row().
-                          '<td><span class="LC_filename">'.$embed_file.'</span></td>'.
-                          '<td><span class="LC_warning">'.&mt('Already exists').'</span></td>'.
-                          &Apache::loncommon::end_data_table_row()."\n";
+        if ($actionurl eq '/adm/dependencies') {
+            my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$embed_file);
+            $modify_output .= &start_data_table_row().
+                              '<td><a href="'.$path.'/'.$embed_file.'" style="text-decoration:none;">'.
+                              '<img src="'.&icon($embed_file).'" border="0" />'.
+                              '&nbsp;<span class="LC_filename">'.$embed_file.'</span></a></td>'.
+                              '<td>'.$size.'</td>'.
+                              '<td>'.$mtime.'</td>'.
+                              '<td><label><input type="checkbox" name="mod_upload_dep" '.
+                              'onclick="toggleBrowse('."'$counter'".')" id="mod_upload_dep_'.
+                              $counter.'" value="'.$counter.'" />'.&mt('Yes').'</label>'.
+                              '<div id="moduploaddep_'.$counter.'" style="display:none;">'.
+                              &embedded_file_element('upload_embedded',$counter,
+                                                     $embed_file,\%mapping,
+                                                     $allfiles,$codebase,'modify').
+                              '</div></td>'.
+                              &end_data_table_row()."\n";
+            $counter ++;
+        } else {
+            $upload_output .= &start_data_table_row().
+                              '<td><span class="LC_filename">'.$embed_file.'</span></td>';
+                              '<td><span class="LC_warning">'.&mt('Already exists').'</span></td>'.
+                              &Apache::loncommon::end_data_table_row()."\n";
+        }
+    }
+    my $delidx = $counter;
+    foreach my $oldfile (sort {lc($a) cmp lc($b)} keys(%unused)) {
+        my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$oldfile);
+        $delete_output .= &start_data_table_row().
+                          '<td><img src="'.&icon($oldfile).'" />'.
+                          '&nbsp;<span class="LC_filename">'.$oldfile.'</span></td>'.
+                          '<td>'.$size.'</td>'.
+                          '<td>'.$mtime.'</td>'.
+                          '<td><label><input type="checkbox" name="del_upload_dep" '.
+                          ' value="'.$delidx.'" />'.&mt('Yes').'</label>'.
+                          &embedded_file_element('upload_embedded',$delidx,
+                                                 $oldfile,\%mapping,$allfiles,
+                                                 $codebase,'delete').'</td>'.
+                          &end_data_table_row()."\n"; 
+        $numunused ++;
+        $delidx ++;
     }
     if ($upload_output) {
         $upload_output = &start_data_table().
                          $upload_output.
                          &end_data_table()."\n";
     }
+    if ($modify_output) {
+        $modify_output = &start_data_table().
+                         &start_data_table_header_row().
+                         '<th>'.&mt('File').'</th>'.
+                         '<th>'.&mt('Size (KB)').'</th>'.
+                         '<th>'.&mt('Modified').'</th>'.
+                         '<th>'.&mt('Upload replacement?').'</th>'.
+                         &end_data_table_header_row().
+                         $modify_output.
+                         &end_data_table()."\n";
+    }
+    if ($delete_output) {
+        $delete_output = &start_data_table().
+                         &start_data_table_header_row().
+                         '<th>'.&mt('File').'</th>'.
+                         '<th>'.&mt('Size (KB)').'</th>'.
+                         '<th>'.&mt('Modified').'</th>'.
+                         '<th>'.&mt('Delete?').'</th>'.
+                         &end_data_table_header_row().
+                         $delete_output.
+                         &end_data_table()."\n";
+    }
     my $applies = 0;
     if ($numremref) {
         $applies ++;
@@ -8722,15 +9479,37 @@ sub ask_for_embedded_content {
     if ($numexisting) {
         $applies ++;
     }
-    if ($num) {
+    if ($counter || $numunused) {
         $output = '<form name="upload_embedded" action="'.$actionurl.'"'.
                   ' method="post" enctype="multipart/form-data">'."\n".
-                  $state.
-                  '<h3>'.&mt('Upload embedded files').
-                  ':</h3>'.$upload_output.'<br />'."\n".
-                  '<input type ="hidden" name="number_embedded_items" value="'.
-                  $num.'" />'."\n";
-        if ($actionurl eq '') {
+                  $state.'<h3>'.$heading.'</h3>'; 
+        if ($actionurl eq '/adm/dependencies') {
+            if ($numnew) {
+                $output .= '<h4>'.&mt('Missing dependencies').'</h4>'.
+                           '<p>'.&mt('The following files need to be uploaded.').'</p>'."\n".
+                           $upload_output.'<br />'."\n";
+            }
+            if ($numexisting) {
+                $output .= '<h4>'.&mt('Uploaded dependencies (in use)').'</h4>'.
+                           '<p>'.&mt('Upload a new file to replace the one currently in use.').'</p>'."\n".
+                           $modify_output.'<br />'."\n";
+                           $buttontext = &mt('Save changes');
+            }
+            if ($numunused) {
+                $output .= '<h4>'.&mt('Unused files').'</h4>'.
+                           '<p>'.&mt('The following uploaded files are no longer used.').'</p>'."\n".
+                           $delete_output.'<br />'."\n";
+                           $buttontext = &mt('Save changes');
+            }
+        } else {
+            $output .= $upload_output.'<br />'."\n";
+        }
+        $output .= '<input type ="hidden" name="number_embedded_items" value="'.
+                   $counter.'" />'."\n";
+        if ($actionurl eq '/adm/dependencies') { 
+            $output .= '<input type ="hidden" name="number_newemb_items" value="'.
+                       $numnew.'" />'."\n";
+        } elsif ($actionurl eq '') {
             $output .=  '<input type="hidden" name="phase" value="three" />';
         }
     } elsif ($applies) {
@@ -8758,13 +9537,13 @@ sub ask_for_embedded_content {
         $output .= $upload_output.'<br />';
     }
     my ($pathchange_output,$chgcount);
-    $chgcount = $num;
+    $chgcount = $counter;
     if (keys(%pathchanges) > 0) {
         foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%pathchanges)) {
-            if ($num) {
+            if ($counter) {
                 $output .= &embedded_file_element('pathchange',$chgcount,
                                                   $embed_file,\%mapping,
-                                                  $allfiles,$codebase);
+                                                  $allfiles,$codebase,'change');
             } else {
                 $pathchange_output .= 
                     &start_data_table_row().
@@ -8773,14 +9552,14 @@ sub ask_for_embedded_content {
                     '<td>'.$mapping{$embed_file}.'</td>'.
                     '<td>'.$embed_file.
                     &embedded_file_element('pathchange',$numpathchg,$embed_file,
-                                           \%mapping,$allfiles,$codebase).
+                                           \%mapping,$allfiles,$codebase,'change').
                     '</td>'.&end_data_table_row();
             }
             $numpathchg ++;
             $chgcount ++;
         }
     }
-    if ($num) {
+    if ($counter) {
         if ($numpathchg) {
             $output .= '<input type ="hidden" name="number_pathchange_items" value="'.
                        $numpathchg.'" />'."\n";
@@ -8790,9 +9569,10 @@ sub ask_for_embedded_content {
             $output .= '<input type="hidden" name="phase" value="three" />'."\n";
         } elsif ($actionurl eq '/adm/portfolio' || $actionurl eq '/adm/coursegrp_portfolio') {
             $output .= '<input type="hidden" name="action" value="upload_embedded" />';
+        } elsif ($actionurl eq '/adm/dependencies') {
+            $output .= '<input type="hidden" name="action" value="process_changes" />';
         }
-        $output .=  '<input type ="submit" value="'.&mt('Upload Listed Files').'" />'."\n".
-                    &mt('(only files for which a location has been provided will be uploaded)').'</form>'."\n";
+        $output .=  '<input type ="submit" value="'.$buttontext.'" />'."\n".'</form>'."\n";
     } elsif ($numpathchg) {
         my %pathchange = ();
         $output .= &modify_html_form('pathchange',$actionurl,$state,\%pathchange,$pathchange_output);
@@ -8800,15 +9580,15 @@ sub ask_for_embedded_content {
             $output .= '<p>'.&mt('or').'</p>'; 
         } 
     }
-    return ($output,$num,$numpathchg);
+    return ($output,$counter,$numpathchg);
 }
 
 sub embedded_file_element {
-    my ($context,$num,$embed_file,$mapping,$allfiles,$codebase) = @_;
+    my ($context,$num,$embed_file,$mapping,$allfiles,$codebase,$type) = @_;
     return unless ((ref($mapping) eq 'HASH') && (ref($allfiles) eq 'HASH') &&
                    (ref($codebase) eq 'HASH'));
     my $output;
-    if ($context eq 'upload_embedded') {
+    if (($context eq 'upload_embedded') && ($type ne 'delete')) {
        $output = '<input name="embedded_item_'.$num.'" type="file" value="" />'."\n";
     }
     $output .= '<input name="embedded_orig_'.$num.'" type="hidden" value="'.
@@ -8835,6 +9615,50 @@ sub embedded_file_element {
     return $output;
 }
 
+sub get_dependency_details {
+    my ($currfile,$currsubfile,$embed_file) = @_;
+    my ($size,$mtime,$showsize,$showmtime);
+    if ((ref($currfile) eq 'HASH') && (ref($currsubfile))) {
+        if ($embed_file =~ m{/}) {
+            my ($path,$fname) = split(/\//,$embed_file);
+            if (ref($currsubfile->{$path}{$fname}) eq 'ARRAY') {
+                ($size,$mtime) = @{$currsubfile->{$path}{$fname}};
+            }
+        } else {
+            if (ref($currfile->{$embed_file}) eq 'ARRAY') {
+                ($size,$mtime) = @{$currfile->{$embed_file}};
+            }
+        }
+        $showsize = $size/1024.0;
+        $showsize = sprintf("%.1f",$showsize);
+        if ($mtime > 0) {
+            $showmtime = &Apache::lonlocal::locallocaltime($mtime);
+        }
+    }
+    return ($showsize,$showmtime);
+}
+
+sub ask_embedded_js {
+    return <<"END";
+<script type="text/javascript"">
+// <![CDATA[
+function toggleBrowse(counter) {
+    var chkboxid = document.getElementById('mod_upload_dep_'+counter);
+    var fileid = document.getElementById('embedded_item_'+counter);
+    var uploaddivid = document.getElementById('moduploaddep_'+counter);
+    if (chkboxid.checked == true) {
+        uploaddivid.style.display='block';
+    } else {
+        uploaddivid.style.display='none';
+        fileid.value = '';
+    }
+}
+// ]]>
+</script>
+
+END
+}
+
 sub upload_embedded {
     my ($context,$dirpath,$uname,$udom,$dir_root,$url_root,$group,$disk_quota,
         $current_disk_usage,$hiddenstate,$actionurl) = @_;
@@ -8893,7 +9717,6 @@ sub upload_embedded {
             $output .= &mt('File name not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2).'<br />';
             next;
         }
-
         $env{'form.embedded_item_'.$i.'.filename'}=$fname;
         if ($context eq 'portfolio') {
             my $result;
@@ -8950,13 +9773,15 @@ sub upload_embedded {
             if (!open($fh,'>'.$dest)) {
                 &Apache::lonnet::logthis('Failed to create '.$dest);
                 $output .= '<span class="LC_error">'.
-                           &mt('An error occurred while trying to upload [_1] for embedded element [_2].',$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
+                           &mt('An error occurred while trying to upload [_1] for embedded element [_2].',
+                               $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
                            '</span><br />';
             } else {
                 if (!print $fh $env{'form.embedded_item_'.$i}) {
                     &Apache::lonnet::logthis('Failed to write to '.$dest);
                     $output .= '<span class="LC_error">'.
-                              &mt('An error occurred while writing the file [_1] for embedded element [_2].',$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
+                              &mt('An error occurred while writing the file [_1] for embedded element [_2].',
+                                  $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
                               '</span><br />';
                 } else {
                     $output .= &mt('Uploaded [_1]','<span class="LC_filename">'.
@@ -8978,15 +9803,17 @@ sub upload_embedded {
     }
     $output .= &modify_html_form('upload_embedded',$actionurl,$hiddenstate,\%pathchange);
     $returnflag = 'ok';
-    if (keys(%pathchange) > 0) {
+    my $numpathchgs = scalar(keys(%pathchange));
+    if ($numpathchgs > 0) {
         if ($context eq 'portfolio') {
             $output .= '<p>'.&mt('or').'</p>';
         } elsif ($context eq 'testbank') {
-            $output .=  '<p>'.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).','<a href="javascript:document.testbankForm.submit();">','</a>').'</p>';
+            $output .=  '<p>'.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).',
+                                  '<a href="javascript:document.testbankForm.submit();">','</a>').'</p>';
             $returnflag = 'modify_orightml';
         }
     }
-    return ($output.$footer,$returnflag);
+    return ($output.$footer,$returnflag,$numpathchgs);
 }
 
 sub modify_html_form {
@@ -9021,7 +9848,7 @@ sub modify_html_form {
                     '<input type="hidden" name="embedded_orig_'.$i.'" value="'.
                     &escape($env{'form.embedded_orig_'.$i}).'" /></td>'.
                     &end_data_table_row();
-            } 
+            }
         }
     } else {
         $modifyform = $pathchgtable;
@@ -9032,6 +9859,9 @@ sub modify_html_form {
         }
     }
     if ($modifyform) {
+        if ($actionurl eq '/adm/dependencies') {
+            $hiddenstate .= '<input type="hidden" name="action" value="modifyhrefs" />';
+        }
         return '<h3>'.&mt('Changes in content of HTML file required').'</h3>'."\n".
                '<p>'.&mt('Changes need to be made to the reference(s) used for one or more of the dependencies, if your HTML file is to work correctly:').'<ol>'."\n".
                '<li>'.&mt('For consistency between the reference(s) and the location of the corresponding stored file within LON-CAPA.').'</li>'."\n".
@@ -9060,23 +9890,55 @@ sub modify_html_refs {
         $container = $env{'form.container'};
     } elsif ($context eq 'coursedoc') {
         $container = $env{'form.primaryurl'};
+    } elsif ($context eq 'manage_dependencies') {
+        (undef,undef,$container) = &Apache::lonnet::decode_symb($env{'form.symb'});
+        $container = "/$container";
     } else {
         $container = $Apache::lonnet::perlvar{'lonDocRoot'}.$env{'form.filename'};
     }
     my (%allfiles,%codebase,$output,$content);
     my @changes = &get_env_multiple('form.namechange');
-    return unless (@changes > 0);
-    if (($context eq 'portfolio') || ($context eq 'coursedoc')) {
-        return unless ($container =~ m{^/uploaded/\Q$udom\E/\Q$uname\E/});
+    unless (@changes > 0) {
+        if (wantarray) {
+            return ('',0,0); 
+        } else {
+            return;
+        }
+    }
+    if (($context eq 'portfolio') || ($context eq 'coursedoc') || 
+        ($context eq 'manage_dependencies')) {
+        unless ($container =~ m{^/uploaded/\Q$udom\E/\Q$uname\E/}) {
+            if (wantarray) {
+                return ('',0,0);
+            } else {
+                return;
+            }
+        } 
         $content = &Apache::lonnet::getfile($container);
-        return if ($content eq '-1');
+        if ($content eq '-1') {
+            if (wantarray) {
+                return ('',0,0);
+            } else {
+                return;
+            }
+        }
     } else {
-        return unless ($container =~ /^\Q$dir_root\E/); 
+        unless ($container =~ /^\Q$dir_root\E/) {
+            if (wantarray) {
+                return ('',0,0);
+            } else {
+                return;
+            }
+        } 
         if (open(my $fh,"<$container")) {
             $content = join('', <$fh>);
             close($fh);
         } else {
-            return;
+            if (wantarray) {
+                return ('',0,0);
+            } else {
+                return;
+            }
         }
     }
     my ($count,$codebasecount) = (0,0);
@@ -9110,13 +9972,14 @@ sub modify_html_refs {
             }
             if ($count || $codebasecount) {
                 my $saveresult;
-                if ($context eq 'portfolio' || $context eq 'coursedoc') {
+                if (($context eq 'portfolio') || ($context eq 'coursedoc') || 
+                    ($context eq 'manage_dependencies')) {
                     my $url = &Apache::lonnet::store_edited_file($container,$content,$udom,$uname,\$saveresult);
                     if ($url eq $container) {
                         my ($fname) = ($container =~ m{/([^/]+)$});
                         $output = '<p>'.&mt('Updated [quant,_1,reference] in [_2].',
                                             $count,'<span class="LC_filename">'.
-                                            $fname.'</span>').'</p>'; 
+                                            $fname.'</span>').'</p>';
                     } else {
                          $output = '<p class="LC_error">'.
                                    &mt('Error: update failed for: [_1].',
@@ -9143,7 +10006,11 @@ sub modify_html_refs {
                      ' to modify references: '.$parse_result);
         }
     }
-    return $output;
+    if (wantarray) {
+        return ($output,$count,$codebasecount);
+    } else {
+        return $output;
+    }
 }
 
 sub check_for_existing {
@@ -9299,6 +10166,1162 @@ sub check_for_traversal {
     return $cleanpath;
 }
 
+sub is_archive_file {
+    my ($mimetype) = @_;
+    if (($mimetype eq 'application/octet-stream') ||
+        ($mimetype eq 'application/x-stuffit') ||
+        ($mimetype =~ m{^application/(x\-)?(compressed|tar|zip|tgz|gz|gtar|gzip|gunzip|bz|bz2|bzip2)})) {
+        return 1;
+    }
+    return;
+}
+
+sub decompress_form {
+    my ($mimetype,$archiveurl,$action,$noextract,$hiddenelements,$dirlist) = @_;
+    my %lt = &Apache::lonlocal::texthash (
+        this => 'This file is an archive file.',
+        camt => 'This file is a Camtasia archive file.',
+        itsc => 'Its contents are as follows:',
+        youm => 'You may wish to extract its contents.',
+        extr => 'Extract contents',
+        auto => 'LON-CAPA can process the files automatically, or you can decide how each should be handled.',
+        proa => 'Process automatically?',
+        yes  => 'Yes',
+        no   => 'No',
+        fold => 'Title for folder containing movie',
+        movi => 'Title for page containing embedded movie', 
+    );
+    my $fileloc = &Apache::lonnet::filelocation(undef,$archiveurl);
+    my ($is_camtasia,$topdir,%toplevel,@paths);
+    my $info = &list_archive_contents($fileloc,\@paths);
+    if (@paths) {
+        foreach my $path (@paths) {
+            $path =~ s{^/}{};
+            if ($path =~ m{^([^/]+)/$}) {
+                $topdir = $1;
+            }
+            if ($path =~ m{^([^/]+)/}) {
+                $toplevel{$1} = $path;
+            } else {
+                $toplevel{$path} = $path;
+            }
+        }
+    }
+    if ($mimetype =~ m{^application/(x\-)?(compressed|zip)}) {
+        my @camtasia = ("$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);
+        if (@diffs == 0) {
+            $is_camtasia = 1;
+        }
+    }
+    my $output;
+    if ($is_camtasia) {
+        $output = <<"ENDCAM";
+<script type="text/javascript" language="Javascript">
+// <![CDATA[
+
+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) {
+
+                document.getElementById('camtasia_titles').style.display='block';
+            } else {
+                document.getElementById('camtasia_titles').style.display='none';
+            }
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+<p>$lt{'camt'}</p>
+ENDCAM
+    } else {
+        $output = '<p>'.$lt{'this'};
+        if ($info eq '') {
+            $output .= ' '.$lt{'youm'}.'</p>'."\n";
+        } else {
+            $output .= ' '.$lt{'itsc'}.'</p>'."\n".
+                       '<div><pre>'.$info.'</pre></div>';
+        }
+    }
+    $output .= '<form name="uploaded_decompress" action="'.$action.'" method="post">'."\n";
+    my $duplicates;
+    my $num = 0;
+    if (ref($dirlist) eq 'ARRAY') {
+        foreach my $item (@{$dirlist}) {
+            if (ref($item) eq 'ARRAY') {
+                if (exists($toplevel{$item->[0]})) {
+                    $duplicates .= 
+                        &start_data_table_row().
+                        '<td><label><input type="radio" name="archive_overwrite_'.$num.'" '.
+                        'value="0" checked="checked" />'.&mt('No').'</label>'.
+                        '&nbsp;<label><input type="radio" name="archive_overwrite_'.$num.'" '.
+                        'value="1" />'.&mt('Yes').'</label>'.
+                        '<input type="hidden" name="archive_overwrite_name_'.$num.'" value="'.$item->[0].'" /></td>'."\n".
+                        '<td>'.$item->[0].'</td>';
+                    if ($item->[2]) {
+                        $duplicates .= '<td>'.&mt('Directory').'</td>';
+                    } else {
+                        $duplicates .= '<td>'.&mt('File').'</td>';
+                    }
+                    $duplicates .= '<td>'.$item->[3].'</td>'.
+                                   '<td>'.
+                                   &Apache::lonlocal::locallocaltime($item->[4]).
+                                   '</td>'.
+                                   &end_data_table_row();
+                    $num ++;
+                }
+            }
+        }
+    }
+    my $itemcount;
+    if (@paths > 0) {
+        $itemcount = scalar(@paths);
+    } else {
+        $itemcount = 1;
+    }
+    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" />'.
+                   $lt{'yes'}.'</label>&nbsp;<label>'.
+                   '<input type="radio" name="autoextract_camtasia" value="0" onclick="javascript:camtasiaToggle();" />'.
+                   $lt{'no'}.'</label></span><br />'.
+                   '<div id="camtasia_titles" style="display:block">'.
+                   &Apache::lonhtmlcommon::start_pick_box().
+                   &Apache::lonhtmlcommon::row_title($lt{'fold'}).
+                   '<input type="textbox" name="camtasia_foldername" value="'.$env{'form.comment'}.'" />'."\n".
+                   &Apache::lonhtmlcommon::row_closure().
+                   &Apache::lonhtmlcommon::row_title($lt{'movi'}).
+                   '<input type="textbox" name="camtasia_moviename" value="" />'."\n".
+                   &Apache::lonhtmlcommon::row_closure(1).
+                   &Apache::lonhtmlcommon::end_pick_box().
+                   '</div>';
+    }
+    $output .= 
+        '<input type="hidden" name="archive_overwrite_total" value="'.$num.'" />'.
+        '<input type="hidden" name="archive_itemcount" value="'.$itemcount.'" />'.
+        "\n";
+    if ($duplicates ne '') {
+        $output .= '<p><span class="LC_warning">'.
+                   &mt('Warning: decompression of the archive will overwrite the following items which already exist:').'</span><br />'.  
+                   &start_data_table().
+                   &start_data_table_header_row().
+                   '<th>'.&mt('Overwrite?').'</th>'.
+                   '<th>'.&mt('Name').'</th>'.
+                   '<th>'.&mt('Type').'</th>'.
+                   '<th>'.&mt('Size').'</th>'.
+                   '<th>'.&mt('Last modified').'</th>'.
+                   &end_data_table_header_row().
+                   $duplicates.
+                   &end_data_table().
+                   '</p>';
+    }
+    $output .= '<input type="hidden" name="archiveurl" value="'.$archiveurl.'" />'."\n";
+    if (ref($hiddenelements) eq 'HASH') {
+        foreach my $hidden (sort(keys(%{$hiddenelements}))) {
+            $output .= '<input type="hidden" name="'.$hidden.'" value="'.$hiddenelements->{$hidden}.'" />'."\n";
+        }
+    }
+    $output .= <<"END";
+<br />
+<input type="submit" name="decompress" value="$lt{'extr'}" />
+</form>
+$noextract
+END
+    return $output;
+}
+
+sub decompression_utility {
+    my ($program) = @_;
+    my @utilities = ('tar','gunzip','bunzip2','unzip'); 
+    my $location;
+    if (grep(/^\Q$program\E$/,@utilities)) { 
+        foreach my $dir ('/bin/','/usr/bin/','/usr/local/bin/','/sbin/',
+                         '/usr/sbin/') {
+            if (-x $dir.$program) {
+                $location = $dir.$program;
+                last;
+            }
+        }
+    }
+    return $location;
+}
+
+sub list_archive_contents {
+    my ($file,$pathsref) = @_;
+    my (@cmd,$output);
+    my $needsregexp;
+    if ($file =~ /\.zip$/) {
+        @cmd = (&decompression_utility('unzip'),"-l");
+        $needsregexp = 1;
+    } elsif (($file =~ m/\.tar\.gz$/) ||
+             ($file =~ /\.tgz$/)) {
+        @cmd = (&decompression_utility('tar'),"-ztf");
+    } elsif ($file =~ /\.tar\.bz2$/) {
+        @cmd = (&decompression_utility('tar'),"-jtf");
+    } elsif ($file =~ m|\.tar$|) {
+        @cmd = (&decompression_utility('tar'),"-tf");
+    }
+    if (@cmd) {
+        undef($!);
+        undef($@);
+        if (open(my $fh,"-|", @cmd, $file)) {
+            while (my $line = <$fh>) {
+                $output .= $line;
+                chomp($line);
+                my $item;
+                if ($needsregexp) {
+                    ($item) = ($line =~ /^\s*\d+\s+[\d\-]+\s+[\d:]+\s*(.+)$/); 
+                } else {
+                    $item = $line;
+                }
+                if ($item ne '') {
+                    unless (grep(/^\Q$item\E$/,@{$pathsref})) {
+                        push(@{$pathsref},$item);
+                    } 
+                }
+            }
+            close($fh);
+        }
+    }
+    return $output;
+}
+
+sub decompress_uploaded_file {
+    my ($file,$dir) = @_;
+    &Apache::lonnet::appenv({'cgi.file' => $file});
+    &Apache::lonnet::appenv({'cgi.dir' => $dir});
+    my $result = &Apache::lonnet::ssi_body('/cgi-bin/decompress.pl');
+    my ($handle) = ($env{'user.environment'} =~m{/([^/]+)\.id$});
+    my $lonidsdir = $Apache::lonnet::perlvar{'lonIDsDir'};
+    &Apache::lonnet::transfer_profile_to_env($lonidsdir,$handle,1);
+    my $decompressed = $env{'cgi.decompressed'};
+    &Apache::lonnet::delenv('cgi.file');
+    &Apache::lonnet::delenv('cgi.dir');
+    &Apache::lonnet::delenv('cgi.decompressed');
+    return ($decompressed,$result);
+}
+
+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)$/) {
+        $error = &mt('File name not a supported archive file type.').
+                 '<br />'.&mt('File name should end with one of: [_1].',
+                              '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz');
+    } else {
+        my $docuhome = &Apache::lonnet::homeserver($docuname,$docudom);
+        if ($docuhome eq 'no_host') {
+            $error = &mt('Could not determine home server for course.');
+        } else {
+            my @ids=&Apache::lonnet::current_machine_ids();
+            my $currdir = "$dir_root/$destination";
+            if (grep(/^\Q$docuhome\E$/,@ids)) {
+                $dir = &LONCAPA::propath($docudom,$docuname).
+                       "$dir_root/$destination";
+            } else {
+                $dir = $Apache::lonnet::perlvar{'lonDocRoot'}.
+                       "$dir_root/$docudom/$docuname/$destination";
+                unless (&Apache::lonnet::repcopy_userfile("$dir/$file") eq 'ok') {
+                    $error = &mt('Archive file not found.');
+                }
+            }
+            my (@to_overwrite,@to_skip);
+            if ($env{'form.archive_overwrite_total'} > 0) {
+                my $total = $env{'form.archive_overwrite_total'};
+                for (my $i=0; $i<$total; $i++) {
+                    if ($env{'form.archive_overwrite_'.$i} == 1) {
+                        push(@to_overwrite,$env{'form.archive_overwrite_name_'.$i});
+                    } elsif ($env{'form.archive_overwrite_'.$i} == 0) {
+                        push(@to_skip,$env{'form.archive_overwrite_name_'.$i});
+                    }
+                }
+            }
+            my $numskip = scalar(@to_skip);
+            if (($numskip > 0) && 
+                ($numskip == $env{'form.archive_itemcount'})) {
+                $warning = &mt('All items in the archive file already exist, and no overwriting of existing files has been requested.');         
+            } elsif ($dir eq '') {
+                $error = &mt('Directory containing archive file unavailable.');
+            } elsif (!$error) {
+                my ($decompressed,$display);
+                if ($numskip > 0) {
+                    my $tempdir = time.'_'.$$.int(rand(10000));
+                    mkdir("$dir/$tempdir",0755);
+                    system("mv $dir/$file $dir/$tempdir/$file");
+                    ($decompressed,$display) = 
+                        &decompress_uploaded_file($file,"$dir/$tempdir");
+                    foreach my $item (@to_skip) {
+                        if (($item ne '') && ($item !~ /\.\./)) {
+                            if (-f "$dir/$tempdir/$item") { 
+                                unlink("$dir/$tempdir/$item");
+                            } elsif (-d "$dir/$tempdir/$item") {
+                                system("rm -rf $dir/$tempdir/$item");
+                            }
+                        }
+                    }
+                    system("mv $dir/$tempdir/* $dir");
+                    rmdir("$dir/$tempdir");   
+                } else {
+                    ($decompressed,$display) = 
+                        &decompress_uploaded_file($file,$dir);
+                }
+                if ($decompressed eq 'ok') {
+                    $output = '<p class="LC_info">'.
+                              &mt('Files extracted successfully from archive.').
+                              '</p>'."\n";
+                    my ($warning,$result,@contents);
+                    my ($newdirlistref,$newlisterror) =
+                        &Apache::lonnet::dirlist($currdir,$docudom,
+                                                 $docuname,1);
+                    my (%is_dir,%changes,@newitems);
+                    my $dirptr = 16384;
+                    if (ref($newdirlistref) eq 'ARRAY') {
+                        foreach my $dir_line (@{$newdirlistref}) {
+                            my ($item,undef,undef,$testdir)=split(/\&/,$dir_line,5);
+                            unless (($item =~ /^\.+$/) || ($item eq $file) || 
+                                    ((@to_skip > 0) && (grep(/^\Q$item\E$/,@to_skip)))) {
+                                push(@newitems,$item);
+                                if ($dirptr&$testdir) {
+                                    $is_dir{$item} = 1;
+                                }
+                                $changes{$item} = 1;
+                            }
+                        }
+                    }
+                    if (keys(%changes) > 0) {
+                        foreach my $item (sort(@newitems)) {
+                            if ($changes{$item}) {
+                                push(@contents,$item);
+                            }
+                        }
+                    }
+                    if (@contents > 0) {
+                        my $wantform;
+                        unless ($env{'form.autoextract_camtasia'}) {
+                            $wantform = 1;
+                        }
+                        my (%children,%parent,%dirorder,%titles);
+                        my ($count,$datatable) = &get_extracted($docudom,$docuname,
+                                                                $currdir,\%is_dir,
+                                                                \%children,\%parent,
+                                                                \@contents,\%dirorder,
+                                                                \%titles,$wantform);
+                        if ($datatable ne '') {
+                            $output .= &archive_options_form('decompressed',$datatable,
+                                                             $count,$hiddenelem);
+                            my $startcount = 6;
+                            $output .= &archive_javascript($startcount,$count,
+                                                           \%titles,\%children);
+                        }
+                        if ($env{'form.autoextract_camtasia'}) {
+                            my %displayed;
+                            my $total = 1;
+                            $env{'form.archive_directory'} = [];
+                            foreach my $i (sort { $a <=> $b } keys(%dirorder)) {
+                                my $path = join('/',map { $titles{$_}; } @{$dirorder{$i}});
+                                $path =~ s{/$}{};
+                                my $item;
+                                if ($path ne '') {
+                                    $item = "$path/$titles{$i}";
+                                } else {
+                                    $item = $titles{$i};
+                                }
+                                $env{'form.archive_content_'.$i} = "$dir_root/$destination/$item";
+                                if ($item eq $contents[0]) {
+                                    push(@{$env{'form.archive_directory'}},$i);
+                                    $env{'form.archive_'.$i} = 'display';
+                                    $env{'form.archive_title_'.$i} = $env{'form.camtasia_foldername'};
+                                    $displayed{'folder'} = $i;
+                                } elsif ($item eq "$contents[0]/index.html") {
+                                    $env{'form.archive_'.$i} = 'display';
+                                    $env{'form.archive_title_'.$i} = $env{'form.camtasia_moviename'};
+                                    $displayed{'web'} = $i;
+                                } else {
+                                    if ($item eq "$contents[0]/media") {
+                                        push(@{$env{'form.archive_directory'}},$i);
+                                    }
+                                    $env{'form.archive_'.$i} = 'dependency';
+                                }
+                                $total ++;
+                            }
+                            for (my $i=1; $i<$total; $i++) {
+                                next if ($i == $displayed{'web'});
+                                next if ($i == $displayed{'folder'});
+                                $env{'form.archive_dependent_on_'.$i} = $displayed{'web'};
+                            }
+                            $env{'form.phase'} = 'decompress_cleanup';
+                            $env{'form.archivedelete'} = 1;
+                            $env{'form.archive_count'} = $total-1;
+                            $output .=
+                                &process_extracted_files('coursedocs',$docudom,
+                                                         $docuname,$destination,
+                                                         $dir_root,$hiddenelem);
+                        }
+                    } else {
+                        $warning = &mt('No new items extracted from archive file.');
+                    }
+                } else {
+                    $output = $display;
+                    $error = &mt('An error occurred during extraction from the archive file.');
+                }
+            }
+        }
+    }
+    if ($error) {
+        $output .= '<p class="LC_error">'.&mt('Not extracted.').'<br />'.
+                   $error.'</p>'."\n";
+    }
+    if ($warning) {
+        $output .= '<p class="LC_warning">'.$warning.'</p>'."\n";
+    }
+    return $output;
+}
+
+sub get_extracted {
+    my ($docudom,$docuname,$currdir,$is_dir,$children,$parent,$contents,$dirorder,
+        $titles,$wantform) = @_;
+    my $count = 0;
+    my $depth = 0;
+    my $datatable;
+    my @hierarchy;
+    return unless ((ref($is_dir) eq 'HASH') && (ref($children) eq 'HASH') &&
+                   (ref($parent) eq 'HASH') && (ref($contents) eq 'ARRAY') &&
+                   (ref($dirorder) eq 'HASH') && (ref($titles) eq 'HASH'));
+    foreach my $item (@{$contents}) {
+        $count ++;
+        @{$dirorder->{$count}} = @hierarchy;
+        $titles->{$count} = $item;
+        &archive_hierarchy($depth,$count,$parent,$children);
+        if ($wantform) {
+            $datatable .= &archive_row($is_dir->{$item},$item,
+                                       $currdir,$depth,$count);
+        }
+        if ($is_dir->{$item}) {
+            $depth ++;
+            push(@hierarchy,$count);
+            $parent->{$depth} = $count;
+            $datatable .=
+                &recurse_extracted_archive("$currdir/$item",$docudom,$docuname,
+                                           \$depth,\$count,\@hierarchy,$dirorder,
+                                           $children,$parent,$titles,$wantform);
+            $depth --;
+            pop(@hierarchy);
+        }
+    }
+    return ($count,$datatable);
+}
+
+sub recurse_extracted_archive {
+    my ($currdir,$docudom,$docuname,$depth,$count,$hierarchy,$dirorder,
+        $children,$parent,$titles,$wantform) = @_;
+    my $result='';
+    unless ((ref($depth)) && (ref($count)) && (ref($hierarchy) eq 'ARRAY') &&
+            (ref($children) eq 'HASH') && (ref($parent) eq 'HASH') &&
+            (ref($dirorder) eq 'HASH')) {
+        return $result;
+    }
+    my $dirptr = 16384;
+    my ($newdirlistref,$newlisterror) =
+        &Apache::lonnet::dirlist($currdir,$docudom,$docuname,1);
+    if (ref($newdirlistref) eq 'ARRAY') {
+        foreach my $dir_line (@{$newdirlistref}) {
+            my ($item,undef,undef,$testdir)=split(/\&/,$dir_line,5);
+            unless ($item =~ /^\.+$/) {
+                $$count ++;
+                @{$dirorder->{$$count}} = @{$hierarchy};
+                $titles->{$$count} = $item;
+                &archive_hierarchy($$depth,$$count,$parent,$children);
+
+                my $is_dir;
+                if ($dirptr&$testdir) {
+                    $is_dir = 1;
+                }
+                if ($wantform) {
+                    $result .= &archive_row($is_dir,$item,$currdir,$$depth,$$count);
+                }
+                if ($is_dir) {
+                    $$depth ++;
+                    push(@{$hierarchy},$$count);
+                    $parent->{$$depth} = $$count;
+                    $result .=
+                        &recurse_extracted_archive("$currdir/$item",$docudom,
+                                                   $docuname,$depth,$count,
+                                                   $hierarchy,$dirorder,$children,
+                                                   $parent,$titles,$wantform);
+                    $$depth --;
+                    pop(@{$hierarchy});
+                }
+            }
+        }
+    }
+    return $result;
+}
+
+sub archive_hierarchy {
+    my ($depth,$count,$parent,$children) =@_;
+    if ((ref($parent) eq 'HASH') && (ref($children) eq 'HASH')) {
+        if (exists($parent->{$depth})) {
+             $children->{$parent->{$depth}} .= $count.':';
+        }
+    }
+    return;
+}
+
+sub archive_row {
+    my ($is_dir,$item,$currdir,$depth,$count) = @_;
+    my ($name) = ($item =~ m{([^/]+)$});
+    my %choices = &Apache::lonlocal::texthash (
+                                       'display'    => 'Add as file',
+                                       'dependency' => 'Include as dependency',
+                                       'discard'    => 'Discard',
+                                      );
+    if ($is_dir) {
+        $choices{'display'} = &mt('Add as folder'); 
+    }
+    my $output = &start_data_table_row().'<td align="right">'.$count.'</td>'."\n";
+    my $offset = 0;
+    foreach my $action ('display','dependency','discard') {
+        $offset ++;
+        if ($action ne 'display') {
+            $offset ++;
+        }  
+        $output .= '<td><span class="LC_nobreak">'.
+                   '<label><input type="radio" name="archive_'.$count.
+                   '" id="archive_'.$action.'_'.$count.'" value="'.$action.'"';
+        my $text = $choices{$action};
+        if ($is_dir) {
+            $output .= ' onclick="javascript:propagateCheck(this.form,'."'$count'".');"';
+            if ($action eq 'display') {
+                $text = &mt('Add as folder');
+            }
+        } else {
+            $output .= ' onclick="javascript:dependencyCheck(this.form,'."$count,$offset".');"';
+
+        }
+        $output .= ' />&nbsp;'.$choices{$action}.'</label></span>';
+        if ($action eq 'dependency') {
+            $output .= '<div id="arc_depon_'.$count.'" style="display:none;">'."\n".
+                       &mt('Used by:').'&nbsp;<select name="archive_dependent_on_'.$count.'" '.
+                       'onchange="propagateSelect(this.form,'."$count,$offset".')">'."\n".
+                       '<option value=""></option>'."\n".
+                       '</select>'."\n".
+                       '</div>';
+        } elsif ($action eq 'display') {
+            $output .= '<div id="arc_title_'.$count.'" style="display:none;">'."\n".
+                       &mt('Title:').'&nbsp;<input type="text" name="archive_title_'.$count.'" id="archive_title_'.$count.'" />'."\n".
+                       '</div>';
+        }
+        $output .= '</td>';
+    }
+    $output .= '<td><input type="hidden" name="archive_content_'.$count.'" value="'.
+               &HTML::Entities::encode("$currdir/$item",'"<>&').'" />'.('&nbsp;' x 2);
+    for (my $i=0; $i<$depth; $i++) {
+        $output .= ('<img src="/adm/lonIcons/whitespace1.gif" class="LC_docs_spacer" alt="" />' x2)."\n";
+    }
+    if ($is_dir) {
+        $output .= '<img src="/adm/lonIcons/navmap.folder.open.gif" alt="" />&nbsp;'."\n".
+                   '<input type="hidden" name="archive_directory" value="'.$count.'" />'."\n";
+    } else {
+        $output .= '<input type="hidden" name="archive_file" value="'.$count.'" />'."\n";
+    }
+    $output .= '&nbsp;'.$name.'</td>'."\n".
+               &end_data_table_row();
+    return $output;
+}
+
+sub archive_options_form {
+    my ($form,$display,$count,$hiddenelem) = @_;
+    my %lt = &Apache::lonlocal::texthash(
+               perm => 'Permanently remove archive file?',
+               hows => 'How should each extracted item be incorporated in the course?',
+               cont => 'Content actions for all',
+               addf => 'Add as folder/file',
+               incd => 'Include as dependency for a displayed file',
+               disc => 'Discard',
+               no   => 'No',
+               yes  => 'Yes',
+               save => 'Save',
+    );
+    my $output = <<"END";
+<form name="$form" method="post" action="">
+<p><span class="LC_nobreak">$lt{'perm'}&nbsp;
+<label>
+  <input type="radio" name="archivedelete" value="0" checked="checked" />$lt{'no'}
+</label>
+&nbsp;
+<label>
+  <input type="radio" name="archivedelete" value="1" />$lt{'yes'}</label>
+</span>
+</p>
+<input type="hidden" name="phase" value="decompress_cleanup" />
+<br />$lt{'hows'}
+<div class="LC_columnSection">
+  <fieldset>
+    <legend>$lt{'cont'}</legend>
+    <input type="button" value="$lt{'addf'}" onclick="javascript:checkAll(document.$form,'display');" /> 
+    &nbsp;&nbsp;<input type="button" value="$lt{'incd'}" onclick="javascript:checkAll(document.$form,'dependency');" />
+    &nbsp;&nbsp;<input type="button" value="$lt{'disc'}" onclick="javascript:checkAll(document.$form,'discard');" />
+  </fieldset>
+</div>
+END
+    return $output.
+           &start_data_table()."\n".
+           $display."\n".
+           &end_data_table()."\n".
+           '<input type="hidden" name="archive_count" value="'.$count.'" />'.
+           $hiddenelem.
+           '<br /><input type="submit" name="archive_submit" value="'.$lt{'save'}.'" />'.
+           '</form>';
+}
+
+sub archive_javascript {
+    my ($startcount,$numitems,$titles,$children) = @_;
+    return unless ((ref($titles) eq 'HASH') && (ref($children) eq 'HASH'));
+    my $maintitle = $env{'form.comment'};
+    my $scripttag = <<START;
+<script type="text/javascript">
+// <![CDATA[
+
+function checkAll(form,prefix) {
+    var idstr =  new RegExp("^archive_"+prefix+"_\\\\d+\$");
+    for (var i=0; i < form.elements.length; i++) {
+        var id = form.elements[i].id;
+        if ((id != '') && (id != undefined)) {
+            if (idstr.test(id)) {
+                if (form.elements[i].type == 'radio') {
+                    form.elements[i].checked = true;
+                    var nostart = i-$startcount;
+                    var offset = nostart%7;
+                    var count = (nostart-offset)/7;    
+                    dependencyCheck(form,count,offset);
+                }
+            }
+        }
+    }
+}
+
+function propagateCheck(form,count) {
+    if (count > 0) {
+        var startelement = $startcount + ((count-1) * 7);
+        for (var j=1; j<6; j++) {
+            if ((j != 2) && (j != 4)) {
+                var item = startelement + j; 
+                if (form.elements[item].type == 'radio') {
+                    if (form.elements[item].checked) {
+                        containerCheck(form,count,j);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+numitems = $numitems
+var titles = new Array(numitems);
+var parents = new Array(numitems);
+for (var i=0; i<numitems; i++) {
+    parents[i] = new Array;
+}
+var maintitle = '$maintitle';
+
+START
+
+    foreach my $container (sort { $a <=> $b } (keys(%{$children}))) {
+        my @contents = split(/:/,$children->{$container});
+        for (my $i=0; $i<@contents; $i ++) {
+            $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n";
+        }
+    }
+
+    foreach my $key (sort { $a <=> $b } (keys(%{$titles}))) {
+        $scripttag .= "titles[$key] = '".$titles->{$key}."';\n";
+    }
+
+    $scripttag .= <<END;
+
+function containerCheck(form,count,offset) {
+    if (count > 0) {
+        dependencyCheck(form,count,offset);
+        var item = (offset+$startcount)+7*(count-1);
+        form.elements[item].checked = true;
+        if(Object.prototype.toString.call(parents[count]) === '[object Array]') {
+            if (parents[count].length > 0) {
+                for (var j=0; j<parents[count].length; j++) {
+                    containerCheck(form,parents[count][j],offset);
+                }
+            }
+        }
+    }
+}
+
+function dependencyCheck(form,count,offset) {
+    if (count > 0) {
+        var chosen = (offset+$startcount)+7*(count-1);
+        var depitem = $startcount + ((count-1) * 7) + 4;
+        var currtype = form.elements[depitem].type;
+        if (form.elements[chosen].value == 'dependency') {
+            document.getElementById('arc_depon_'+count).style.display='block'; 
+            form.elements[depitem].options.length = 0;
+            form.elements[depitem].options[0] = new Option('Select','',true,true);
+            for (var i=1; i<count; i++) {
+                var startelement = $startcount + (i-1) * 7;
+                for (var j=1; j<6; j++) {
+                    if ((j != 2) && (j!= 4)) {
+                        var item = startelement + j;
+                        if (form.elements[item].type == 'radio') {
+                            if (form.elements[item].checked) {
+                                if (form.elements[item].value == 'display') {
+                                    var n = form.elements[depitem].options.length;
+                                    form.elements[depitem].options[n] = new Option(titles[i],i,false,false);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            document.getElementById('arc_depon_'+count).style.display='none';
+            form.elements[depitem].options.length = 0;
+            form.elements[depitem].options[0] = new Option('Select','',true,true);
+        }
+        titleCheck(form,count,offset);
+    }
+}
+
+function propagateSelect(form,count,offset) {
+    if (count > 0) {
+        var item = (1+offset+$startcount)+7*(count-1);
+        var picked = form.elements[item].options[form.elements[item].selectedIndex].value; 
+        if (Object.prototype.toString.call(parents[count]) === '[object Array]') {
+            if (parents[count].length > 0) {
+                for (var j=0; j<parents[count].length; j++) {
+                    containerSelect(form,parents[count][j],offset,picked);
+                }
+            }
+        }
+    }
+}
+
+function containerSelect(form,count,offset,picked) {
+    if (count > 0) {
+        var item = (offset+$startcount)+7*(count-1);
+        if (form.elements[item].type == 'radio') {
+            if (form.elements[item].value == 'dependency') {
+                if (form.elements[item+1].type == 'select-one') {
+                    for (var i=0; i<form.elements[item+1].options.length; i++) {
+                        if (form.elements[item+1].options[i].value == picked) {
+                            form.elements[item+1].selectedIndex = i;
+                            break;
+                        }
+                    }
+                }
+                if (Object.prototype.toString.call(parents[count]) === '[object Array]') {
+                    if (parents[count].length > 0) {
+                        for (var j=0; j<parents[count].length; j++) {
+                            containerSelect(form,parents[count][j],offset,picked);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+function titleCheck(form,count,offset) {
+    if (count > 0) {
+        var chosen = (offset+$startcount)+7*(count-1);
+        var depitem = $startcount + ((count-1) * 7) + 2;
+        var currtype = form.elements[depitem].type;
+        if (form.elements[chosen].value == 'display') {
+            document.getElementById('arc_title_'+count).style.display='block';
+            if ((count==1) && ((parents[count].length > 0) || (numitems == 1))) {
+                document.getElementById('archive_title_'+count).value=maintitle;
+            }
+        } else {
+            document.getElementById('arc_title_'+count).style.display='none';
+            if (currtype == 'text') { 
+                document.getElementById('archive_title_'+count).value='';
+            }
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+END
+    return $scripttag;
+}
+
+sub process_extracted_files {
+    my ($context,$docudom,$docuname,$destination,$dir_root,$hiddenelem) = @_;
+    my $numitems = $env{'form.archive_count'};
+    return unless ($numitems);
+    my @ids=&Apache::lonnet::current_machine_ids();
+    my ($prefix,$pathtocheck,$dir,$ishome,$error,$warning,%toplevelitems,%is_dir,
+        %folders,%containers,%mapinner,%prompttofetch);
+    my $docuhome = &Apache::lonnet::homeserver($docuname,$docudom);
+    if (grep(/^\Q$docuhome\E$/,@ids)) {
+        $prefix = &LONCAPA::propath($docudom,$docuname);
+        $pathtocheck = "$dir_root/$destination";
+        $dir = $dir_root;
+        $ishome = 1;
+    } else {
+        $prefix = $Apache::lonnet::perlvar{'lonDocRoot'};
+        $pathtocheck = "$dir_root/$docudom/$docuname/$destination";
+        $dir = "$dir_root/$docudom/$docuname";    
+    }
+    my $currdir = "$dir_root/$destination";
+    (my $docstype,$mapinner{'0'}) = ($destination =~ m{^(docs|supplemental)/(\w+)/});
+    if ($env{'form.folderpath'}) {
+        my @items = split('&',$env{'form.folderpath'});
+        $folders{'0'} = $items[-2];
+        $containers{'0'}='sequence';
+    } elsif ($env{'form.pagepath'}) {
+        my @items = split('&',$env{'form.pagepath'});
+        $folders{'0'} = $items[-2];
+        $containers{'0'}='page';
+    }
+    my @archdirs = &get_env_multiple('form.archive_directory');
+    if ($numitems) {
+        for (my $i=1; $i<=$numitems; $i++) {
+            my $path = $env{'form.archive_content_'.$i};
+            if ($path =~ m{^\Q$pathtocheck\E/([^/]+)$}) {
+                my $item = $1;
+                $toplevelitems{$item} = $i;
+                if (grep(/^\Q$i\E$/,@archdirs)) {
+                    $is_dir{$item} = 1;
+                }
+            }
+        }
+    }
+    my ($output,%children,%parent,%titles,%dirorder,$result);
+    if (keys(%toplevelitems) > 0) {
+        my @contents = sort(keys(%toplevelitems));
+        (my $count,undef) = &get_extracted($docudom,$docuname,$currdir,\%is_dir,\%children,
+                                           \%parent,\@contents,\%dirorder,\%titles);
+    }
+    my (%referrer,%orphaned,%todelete,%todeletedir,%newdest,%newseqid);
+    if ($numitems) {
+        for (my $i=1; $i<=$numitems; $i++) {
+            my $path = $env{'form.archive_content_'.$i};
+            if ($path =~ /^\Q$pathtocheck\E/) {
+                if ($env{'form.archive_'.$i} eq 'discard') {
+                    if ($prefix ne '' && $path ne '') {
+                        if (-e $prefix.$path) {
+                            if ((@archdirs > 0) && 
+                                (grep(/^\Q$i\E$/,@archdirs))) {
+                                $todeletedir{$prefix.$path} = 1;
+                            } else {
+                                $todelete{$prefix.$path} = 1;
+                            }
+                        }
+                    }
+                } elsif ($env{'form.archive_'.$i} eq 'display') {
+                    my ($docstitle,$title,$url,$outer);
+                    ($title) = ($path =~ m{/([^/]+)$});
+                    $docstitle = $env{'form.archive_title_'.$i};
+                    if ($docstitle eq '') {
+                        $docstitle = $title;
+                    }
+                    $outer = 0;
+                    if (ref($dirorder{$i}) eq 'ARRAY') {
+                        if (@{$dirorder{$i}} > 0) {
+                            foreach my $item (reverse(@{$dirorder{$i}})) {
+                                if ($env{'form.archive_'.$item} eq 'display') {
+                                    $outer = $item;
+                                    last;
+                                }
+                            }
+                        }
+                    }
+                    my ($errtext,$fatal) = 
+                        &LONCAPA::map::mapread('/uploaded/'.$docudom.'/'.$docuname.
+                                               '/'.$folders{$outer}.'.'.
+                                               $containers{$outer});
+                    next if ($fatal);
+                    if ((@archdirs > 0) && (grep(/^\Q$i\E$/,@archdirs))) {
+                        if ($context eq 'coursedocs') {
+                            $mapinner{$i} = time;
+                            $folders{$i} = 'default_'.$mapinner{$i};
+                            $containers{$i} = 'sequence';
+                            my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
+                                      $folders{$i}.'.'.$containers{$i};
+                            my $newidx = &LONCAPA::map::getresidx();
+                            $LONCAPA::map::resources[$newidx]=
+                                $docstitle.':'.$url.':false:normal:res';
+                            push(@LONCAPA::map::order,$newidx);
+                            my ($outtext,$errtext) =
+                                &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
+                                                        $docuname.'/'.$folders{$outer}.
+                                                        '.'.$containers{$outer},1);
+                            $newseqid{$i} = $newidx;
+                            unless ($errtext) {
+                                $result .=  '<li>'.&mt('Folder: [_1] added to course',$docstitle).'</li>'."\n";
+                            }
+                        }
+                    } else {
+                        if ($context eq 'coursedocs') {
+                            my $newidx=&LONCAPA::map::getresidx();
+                            my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
+                                      $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'.
+                                      $title;
+                            if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
+                                mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
+                            }
+                            if (!-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+                                mkdir("$prefix$dir/$docstype/$mapinner{$outer}/$newidx");
+                            }
+                            if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+                                system("mv $prefix$path $prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title");
+                                $newdest{$i} = "$prefix$dir/$docstype/$mapinner{$outer}/$newidx";
+                                unless ($ishome) {
+                                    my $fetch = "$newdest{$i}/$title";
+                                    $fetch =~ s/^\Q$prefix$dir\E//;
+                                    $prompttofetch{$fetch} = 1;
+                                }
+                            }
+                            $LONCAPA::map::resources[$newidx]=
+                                $docstitle.':'.$url.':false:normal:res';
+                            push(@LONCAPA::map::order, $newidx);
+                            my ($outtext,$errtext)=
+                                &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
+                                                        $docuname.'/'.$folders{$outer}.
+                                                        '.'.$containers{$outer},1);
+                            unless ($errtext) {
+                                if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title") {
+                                    $result .= '<li>'.&mt('File: [_1] added to course',$docstitle).'</li>'."\n";
+                                }
+                            }
+                        }
+                    }
+                } elsif ($env{'form.archive_'.$i} eq 'dependency') {
+                    my ($title) = ($path =~ m{/([^/]+)$});
+                    $referrer{$i} = $env{'form.archive_dependent_on_'.$i};
+                    if ($env{'form.archive_'.$referrer{$i}} eq 'display') {
+                        if (ref($dirorder{$i}) eq 'ARRAY') {
+                            my ($itemidx,$fullpath,$relpath);
+                            for (my $j=0; $j<@{$dirorder{$i}}; $j++) {
+                                if (ref($dirorder{$referrer{$i}}) eq 'ARRAY') {
+                                    my $container = $dirorder{$referrer{$i}}->[-1];
+                                    for (my $j=0; $j<@{$dirorder{$i}}; $j++) {
+                                        if ($dirorder{$i}->[$j] eq $container) {
+                                            $itemidx = $j;
+                                        }
+                                    }
+                                }
+                            }
+                            if ($itemidx ne '') {
+                                if (grep(/^\Q$referrer{$i}\E$/,@archdirs)) {
+                                    if ($mapinner{$referrer{$i}}) {
+                                        $fullpath = "$prefix$dir/$docstype/$mapinner{$referrer{$i}}";
+                                        for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+                                            if (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+                                                unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+                                                    $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+                                                    $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+                                                    if (!-e $fullpath) {
+                                                        mkdir($fullpath,0755);
+                                                    }
+                                                }
+                                            } else {
+                                                last;
+                                            }
+                                        }
+                                    }
+                                } elsif ($newdest{$referrer{$i}}) {
+                                    $fullpath = $newdest{$referrer{$i}};
+                                    for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+                                        if ($env{'form.archive_'.$dirorder{$i}->[$j]} eq 'discard') {
+                                            $orphaned{$i} = $env{'form.archive_'.$dirorder{$i}->[$j]};
+                                            last;
+                                        } elsif (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+                                            unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+                                                $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+                                                $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+                                                if (!-e $fullpath) {
+                                                    mkdir($fullpath,0755);
+                                                }
+                                            }
+                                        } else {
+                                            last;
+                                        }
+                                    }
+                                }
+                                if ($fullpath ne '') {
+                                    if (-e "$prefix$path") {
+                                        system("mv $prefix$path $fullpath/$title");
+                                    }
+                                    if (-e "$fullpath/$title") {
+                                        my $showpath;
+                                        if ($relpath ne '') {
+                                            $showpath = "$relpath/$title";
+                                        } else {
+                                            $showpath = "/$title";
+                                        } 
+                                        $result .= '<li>'.&mt('[_1] included as a dependency',$showpath).'</li>'."\n";
+                                    } 
+                                    unless ($ishome) {
+                                        my $fetch = "$fullpath/$title";
+                                        $fetch =~ s/^\Q$prefix$dir\E//; 
+                                        $prompttofetch{$fetch} = 1;
+                                    }
+                                }
+                            }
+                        }
+                    } elsif ($env{'form.archive_'.$referrer{$i}} eq 'discard') {
+                        $warning .= &mt('[_1] is a dependency of [_2], which was discarded.',
+                                        $path,$env{'form.archive_content_'.$referrer{$i}}).'<br />';
+                    }
+                }
+            } else {
+                $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).'<br />'; 
+            }
+        }
+        if (keys(%todelete)) {
+            foreach my $key (keys(%todelete)) {
+                unlink($key);
+            }
+        }
+        if (keys(%todeletedir)) {
+            foreach my $key (keys(%todeletedir)) {
+                rmdir($key);
+            }
+        }
+        foreach my $dir (sort(keys(%is_dir))) {
+            if (($pathtocheck ne '') && ($dir ne ''))  {
+                &cleanup_empty_dirs($prefix."$pathtocheck/$dir");
+            }
+        }
+        if ($result ne '') {
+            $output .= '<ul>'."\n".
+                       $result."\n".
+                       '</ul>';
+        }
+        unless ($ishome) {
+            my $replicationfail;
+            foreach my $item (keys(%prompttofetch)) {
+                my $fetchresult= &Apache::lonnet::reply('fetchuserfile:'.$item,$docuhome);
+                unless ($fetchresult eq 'ok') {
+                    $replicationfail .= '<li>'.$item.'</li>'."\n";
+                }
+            }
+            if ($replicationfail) {
+                $output .= '<p class="LC_error">'.
+                           &mt('Course home server failed to retrieve:').'<ul>'.
+                           $replicationfail.
+                           '</ul></p>';
+            }
+        }
+    } else {
+        $warning = &mt('No items found in archive.');
+    }
+    if ($error) {
+        $output .= '<p class="LC_error">'.&mt('Not extracted.').'<br />'.
+                   $error.'</p>'."\n";
+    }
+    if ($warning) {
+        $output .= '<p class="LC_warning">'.$warning.'</p>'."\n";
+    }
+    return $output;
+}
+
+sub cleanup_empty_dirs {
+    my ($path) = @_;
+    if (($path ne '') && (-d $path)) {
+        if (opendir(my $dirh,$path)) {
+            my @dircontents = grep(!/^\./,readdir($dirh));
+            my $numitems = 0;
+            foreach my $item (@dircontents) {
+                if (-d "$path/$item") {
+                    &recurse_dirs("$path/$item");
+                    if (-e "$path/$item") {
+                        $numitems ++;
+                    }
+                } else {
+                    $numitems ++;
+                }
+            }
+            if ($numitems == 0) {
+                rmdir($path);
+            }
+            closedir($dirh);
+        }
+    }
+    return;
+}
+
+=pod
+
+=item &get_folder_hierarchy()
+
+Provides hierarchy of names of folders/sub-folders containing the current
+item,
+
+Inputs: 3
+     - $navmap - navmaps object
+
+     - $map - url for map (either the trigger itself, or map containing
+                           the resource, which is the trigger).
+
+     - $showitem - 1 => show title for map itself; 0 => do not show.
+
+Outputs: 1 @pathitems - array of folder/subfolder names.
+
+=cut
+
+sub get_folder_hierarchy {
+    my ($navmap,$map,$showitem) = @_;
+    my @pathitems;
+    if (ref($navmap)) {
+        my $mapres = $navmap->getResourceByUrl($map);
+        if (ref($mapres)) {
+            my $pcslist = $mapres->map_hierarchy();
+            if ($pcslist ne '') {
+                my @pcs = split(/,/,$pcslist);
+                foreach my $pc (@pcs) {
+                    if ($pc == 1) {
+                        push(@pathitems,&mt('Main Course Documents'));
+                    } else {
+                        my $res = $navmap->getByMapPc($pc);
+                        if (ref($res)) {
+                            my $title = $res->compTitle();
+                            $title =~ s/\W+/_/g;
+                            if ($title ne '') {
+                                push(@pathitems,$title);
+                            }
+                        }
+                    }
+                }
+            }
+            if ($showitem) {
+                if ($mapres->{ID} eq '0.0') {
+                    push(@pathitems,&mt('Main Course Documents'));
+                } else {
+                    my $maptitle = $mapres->compTitle();
+                    $maptitle =~ s/\W+/_/g;
+                    if ($maptitle ne '') {
+                        push(@pathitems,$maptitle);
+                    }
+                }
+            }
+        }
+    }
+    return @pathitems;
+}
+
 =pod
 
 =item * &get_turnedin_filepath()
@@ -11444,7 +13467,7 @@ sub init_user_environment {
 
 # See if old ID present, if so, remove
 
-    my ($filename,$cookie,$userroles);
+    my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
     my $now=time;
 
     if ($public) {
@@ -11482,7 +13505,8 @@ sub init_user_environment {
     
 # Initialize roles
 
-	$userroles=&Apache::lonnet::rolesinit($domain,$username,$authhost);
+	($userroles,$firstaccenv,$timerintenv) = 
+            &Apache::lonnet::rolesinit($domain,$username,$authhost);
     }
 # ------------------------------------ Check browser type and MathML capability
 
@@ -11557,12 +13581,18 @@ sub init_user_environment {
         }
 
 	$env{'user.environment'} = "$lonids/$cookie.id";
-	
+
 	if (tie(my %disk_env,'GDBM_File',"$lonids/$cookie.id",
 		 &GDBM_WRCREAT(),0640)) {
 	    &_add_to_env(\%disk_env,\%initial_env);
 	    &_add_to_env(\%disk_env,\%userenv,'environment.');
 	    &_add_to_env(\%disk_env,$userroles);
+            if (ref($firstaccenv) eq 'HASH') {
+                &_add_to_env(\%disk_env,$firstaccenv);
+            }
+            if (ref($timerintenv) eq 'HASH') {
+                &_add_to_env(\%disk_env,$timerintenv);
+            }
 	    if (ref($args->{'extra_env'})) {
 		&_add_to_env(\%disk_env,$args->{'extra_env'});
 	    }
@@ -11598,7 +13628,9 @@ sub get_symb {
     my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
     if ($symb eq '') {
         if (!$silent) {
-            $request->print("Unable to handle ambiguous references:$url:.");
+            if (ref($request)) { 
+                $request->print("Unable to handle ambiguous references:$url:.");
+            }
             return ();
         }
     }