--- loncom/interface/loncommon.pm	2008/03/28 14:52:52	1.650
+++ loncom/interface/loncommon.pm	2008/07/08 01:08:57	1.667
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # a pile of common routines
 #
-# $Id: loncommon.pm,v 1.650 2008/03/28 14:52:52 www Exp $
+# $Id: loncommon.pm,v 1.667 2008/07/08 01:08:57 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -67,6 +67,7 @@ use Apache::loncoursedata();
 use Apache::lontexconvert();
 use Apache::lonclonecourse();
 use LONCAPA qw(:DEFAULT :match);
+use DateTime::TimeZone;
 
 # ---------------------------------------------- Designs
 use vars qw(%defaultdesign);
@@ -447,6 +448,25 @@ sub selectstudent_link {
    return '';
 }
 
+sub authorbrowser_javascript {
+    return <<"ENDAUTHORBRW";
+<script type="text/javascript">
+var stdeditbrowser;
+
+function openauthorbrowser(formname,udom) {
+    var url = '/adm/pickauthor?';
+    url += 'form='+formname+'&roledom='+udom;
+    var title = 'Author_Browser';
+    var options = 'scrollbars=1,resizable=1,menubar=0';
+    options += ',width=700,height=600';
+    stdeditbrowser = open(url,title,options,'1');
+    stdeditbrowser.focus();
+}
+
+</script>
+ENDAUTHORBRW
+}
+
 sub coursebrowser_javascript {
     my ($domainfilter,$sec_element,$formname)=@_;
     my $crs_or_grp_alert = &mt('Please select the type of LON-CAPA entity - Course or Group - for which you wish to add/modify a user role');
@@ -584,6 +604,12 @@ sub selectcourse_link {
         '","'.$udomele.'","'.$desc.'","'.$extra_element.'","'.$multflag.'","'.$selecttype.'");'."'>".&mt('Select Course')."</a>";
 }
 
+sub selectauthor_link {
+   my ($form,$udom)=@_;
+   return '<a href="javascript:openauthorbrowser('."'$form','$udom'".');">'.
+          &mt('Select Author').'</a>';
+}
+
 sub check_uncheck_jscript {
     my $jscript = <<"ENDSCRT";
 function checkAll(field) {
@@ -609,6 +635,27 @@ ENDSCRT
     return $jscript;
 }
 
+sub select_timezone {
+   my ($name,$selected,$onchange,$includeempty)=@_;
+   my $output='<select name="'.$name.'" '.$onchange.'>'."\n";
+   if ($includeempty) {
+       $output .= '<option value=""';
+       if (($selected eq '') || ($selected eq 'local')) {
+           $output .= ' selected="selected" ';
+       }
+       $output .= '> </option>';
+   }
+   my @timezones = DateTime::TimeZone->all_names;
+   foreach my $tzone (@timezones) {
+       $output.= '<option value="'.$tzone.'"';
+       if ($tzone eq $selected) {
+           $output.=' selected="selected"';
+       }
+       $output.=">$tzone</option>\n";
+   }
+   $output.="</select>";
+   return $output;
+}
 
 =pod
 
@@ -831,7 +878,7 @@ sub help_open_topic {
 
     # Add the graphic
     my $title = &mt('Online Help');
-    my $helpicon=&lonhttpdurl("/res/adm/pages/help.png");
+    my $helpicon=&lonhttpdurl("/adm/help/help.png");
     $template .= <<"ENDTEMPLATE";
  <a target="_top" href="$link" title="$title"><img src="$helpicon" border="0" alt="(Help: $topic)" /></a>
 ENDTEMPLATE
@@ -2907,10 +2954,14 @@ sub display_languages {
 
 sub preferred_languages {
     my @languages=();
+    if (($env{'request.role.adv'}) && ($env{'form.languages'})) {
+        @languages=(@languages,split(/\s*(\,|\;|\:)\s*/,$env{'form.languages'}));
+    }
     if ($env{'course.'.$env{'request.course.id'}.'.languages'}) {
 	@languages=(@languages,split(/\s*(\,|\;|\:)\s*/,
 	         $env{'course.'.$env{'request.course.id'}.'.languages'}));
     }
+
     if ($env{'environment.languages'}) {
 	@languages=(@languages,
 		    split(/\s*(\,|\;|\:)\s*/,$env{'environment.languages'}));
@@ -3179,7 +3230,11 @@ sub get_student_view_with_retries {
     if (!$ok) {
        $content = '';          # On error return an empty content.
     }
-    return ($content, $response);
+    if (wantarray) {
+       return ($content, $response);
+    } else {
+       return $content;
+    }
 }
 
 =pod
@@ -4514,6 +4569,10 @@ td.LC_menubuttons_img {
   text-align: right;
 }
 
+.LC_roleslog_note {
+  font-size: smaller;
+}
+
 table.LC_aboutme_port {
   border: 0px;
   border-collapse: collapse;
@@ -6980,6 +7039,233 @@ sub get_env_multiple {
     return(@values);
 }
 
+sub ask_for_embedded_content {
+    my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
+    my $upload_output = '
+   <form name="upload_embedded" action="'.$actionurl.'"
+                  method="post" enctype="multipart/form-data">';
+    $upload_output .= $state;
+    $upload_output .= '<b>Upload embedded files</b>:<br />'.&start_data_table();
+
+    my $num = 0;
+    foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%{$allfiles})) {
+        $upload_output .= &start_data_table_row().
+            '<td>'.$embed_file.'</td><td>';
+        if ($args->{'ignore_remote_references'}
+            && $embed_file =~ m{^\w+://}) {
+            $upload_output.='<span class="LC_warning">'.&mt("URL points to other server.").'</span>';
+        } elsif ($args->{'error_on_invalid_names'}
+            && $embed_file ne &Apache::lonnet::clean_filename($embed_file,{'keep_path' => 1,})) {
+
+            $upload_output.='<span class="LC_warning">'.&mt("Invalid characters").'</span>';
+
+        } else {
+            $upload_output .='
+           <input name="embedded_item_'.$num.'" type="file" value="" />
+           <input name="embedded_orig_'.$num.'" type="hidden" value="'.&escape($embed_file).'" />';
+            my $attrib = join(':',@{$$allfiles{$embed_file}});
+            $upload_output .=
+                "\n\t\t".
+                '<input name="embedded_attrib_'.$num.'" type="hidden" value="'.
+                $attrib.'" />';
+            if (exists($$codebase{$embed_file})) {
+                $upload_output .=
+                    "\n\t\t".
+                    '<input name="codebase_'.$num.'" type="hidden" value="'.
+                    &escape($$codebase{$embed_file}).'" />';
+            }
+        }
+        $upload_output .= '</td>'.&Apache::loncommon::end_data_table_row();
+        $num++;
+    }
+    $upload_output .= &Apache::loncommon::end_data_table().'<br />
+   <input type ="hidden" name="number_embedded_items" value="'.$num.'" />
+   <input type ="submit" value="'.&mt('Upload Listed Files').'" />
+   '.&mt('(only files for which a location has been provided will be uploaded)').'
+   </form>';
+    return $upload_output;
+}
+
+sub upload_embedded {
+    my ($context,$dirpath,$uname,$udom,$dir_root,$url_root,$group,$disk_quota,
+        $current_disk_usage) = @_;
+    my $output;
+    for (my $i=0; $i<$env{'form.number_embedded_items'}; $i++) {
+        next if (!exists($env{'form.embedded_item_'.$i.'.filename'}));
+        my $orig_uploaded_filename =
+            $env{'form.embedded_item_'.$i.'.filename'};
+
+        $env{'form.embedded_orig_'.$i} =
+            &unescape($env{'form.embedded_orig_'.$i});
+        my ($path,$fname) =
+            ($env{'form.embedded_orig_'.$i} =~ m{(.*/)([^/]*)});
+        # no path, whole string is fname
+        if (!$fname) { $fname = $env{'form.embedded_orig_'.$i} };
+
+        $path = $env{'form.currentpath'}.$path;
+        $fname = &Apache::lonnet::clean_filename($fname);
+        # See if there is anything left
+        next if ($fname eq '');
+
+        # Check if file already exists as a file or directory.
+        my ($state,$msg);
+        if ($context eq 'portfolio') {
+            my $port_path = $dirpath;
+            if ($group ne '') {
+                $port_path = "groups/$group/$port_path";
+            }
+            ($state,$msg) = &check_for_upload($path,$fname,$group,'embedded_item_'.$i,
+                                              $dir_root,$port_path,$disk_quota,
+                                              $current_disk_usage,$uname,$udom);
+            if ($state eq 'will_exceed_quota'
+                || $state eq 'file_locked'
+                || $state eq 'file_exists' ) {
+                $output .= $msg;
+                next;
+            }
+        } elsif (($context eq 'author') || ($context eq 'testbank')) {
+            ($state,$msg) = &check_for_existing($path,$fname,'embedded_item_'.$i);
+            if ($state eq 'exists') {
+                $output .= $msg;
+                next;
+            }
+        }
+        # Check if extension is valid
+        if (($fname =~ /\.(\w+)$/) &&
+            (&Apache::loncommon::fileembstyle($1) eq 'hdn')) {
+            $output .= &mt('Invalid file extension ([_1]) - reserved for LONCAPA use - rename the file with a different extension and re-upload. ',$1);
+            next;
+        } elsif (($fname =~ /\.(\w+)$/) &&
+                 (!defined(&Apache::loncommon::fileembstyle($1)))) {
+            $output .= &mt('Unrecognized file extension ([_1]) - rename the file with a proper extension and re-upload.',$1);
+            next;
+        } elsif ($fname=~/\.(\d+)\.(\w+)$/) {
+            $output .= &mt('File name not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2);
+            next;
+        }
+
+        $env{'form.embedded_item_'.$i.'.filename'}=$fname;
+        if ($context eq 'portfolio') {
+            my $result=
+                &Apache::lonnet::userfileupload('embedded_item_'.$i,'',
+                                                $dirpath.$path);
+            if ($result !~ m|^/uploaded/|) {
+                $output .= '<span class="LC_error">'
+                      .&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].'
+                           ,$result,$orig_uploaded_filename,$env{'form.embedded_orig_'.$i})
+                      .'</span><br />';
+                next;
+            } else {
+                $output .= '<p>'.&mt('Uploaded [_1]','<span class="LC_filename">'.
+                           $path.$fname.'</span>').'</p>';     
+            }
+        } else {
+# Save the file
+            my $target = $env{'form.embedded_item_'.$i};
+            my $fullpath = $dir_root.$dirpath.'/'.$path;
+            my $dest = $fullpath.$fname;
+            my $url = $url_root.$dirpath.'/'.$path.$fname;
+            my @parts=split(/\//,$fullpath);
+            my $count;
+            my $filepath = $dir_root;
+            for ($count=4;$count<=$#parts;$count++) {
+                $filepath .= "/$parts[$count]";
+                if ((-e $filepath)!=1) {
+                    mkdir($filepath,0770);
+                }
+            }
+            my $fh;
+            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}).
+                           '</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}).
+                              '</span><br />';
+                } else {
+                    if ($context eq 'testbank') {
+                        $output .= &mt('Embedded file uploaded successfully:').
+                                   '&nbsp;<a href="'.$url.'">'.
+                                   $orig_uploaded_filename.'</a><br />';
+                    } else {
+                        $output .= '<font size="+2">'.
+                                   &mt('View embedded file: [_1]','<a href="'.$url.'">'.
+                                   $orig_uploaded_filename.'</a>').'</font><br />';
+                    }
+                }
+                close($fh);
+            }
+        }
+    }
+    return $output;
+}
+
+sub check_for_existing {
+    my ($path,$fname,$element) = @_;
+    my ($state,$msg);
+    if (-d $path.'/'.$fname) {
+        $state = 'exists';
+        $msg = &mt('Unable to upload [_1]. A directory by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>',$path);
+    } elsif (-e $path.'/'.$fname) {
+        $state = 'exists';
+        $msg = &mt('Unable to upload [_1]. A file by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>',$path);
+    }
+    if ($state eq 'exists') {
+        $msg = '<span class="LC_error">'.$msg.'</span><br />';
+    }
+    return ($state,$msg);
+}
+
+sub check_for_upload {
+    my ($path,$fname,$group,$element,$portfolio_root,$port_path,
+        $disk_quota,$current_disk_usage,$uname,$udom) = @_;
+    my $filesize = (length($env{'form.'.$element})) / 1000; #express in k (1024?)
+    my $getpropath = 1;
+    my @dir_list = &Apache::lonnet::dirlist($portfolio_root.$path,$udom,$uname,
+                                            $getpropath);
+    my $found_file = 0;
+    my $locked_file = 0;
+    foreach my $line (@dir_list) {
+        my ($file_name)=split(/\&/,$line,2);
+        if ($file_name eq $fname){
+            $file_name = $path.$file_name;
+            if ($group ne '') {
+                $file_name = $group.$file_name;
+            }
+            $found_file = 1;
+            if (&Apache::lonnet::is_locked($file_name,$udom,$uname) eq 'true') {
+                $locked_file = 1;
+            }
+        }
+    }
+    my $getpropath = 1;
+    if (($current_disk_usage + $filesize) > $disk_quota){
+        my $msg = '<span class="LC_error">'.
+                &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.','<span class="LC_filename">'.$fname.'</span>',$filesize).'</span>'.
+                  '<br />'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage);
+        return ('will_exceed_quota',$msg);
+    } elsif ($found_file) {
+        if ($locked_file) {
+            my $msg = '<span class="LC_error">';
+            $msg .= &mt('Unable to upload [_1]. A locked file by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>','<span class="LC_filename">'.$port_path.$env{'form.currentpath'}.'</span>');
+            $msg .= '</span><br />';
+            $msg .= &mt('You will be able to rename or delete existing [_1] after a grade has been assigned.','<span class="LC_filename">'.$fname.'</span>');
+            return ('file_locked',$msg);
+        } else {
+            my $msg = '<span class="LC_error">';
+            $msg .= &mt('Unable to upload [_1]. A file by that name was found in [_2].','<span class="LC_filename">'.$fname.'</span>',$port_path.$env{'form.currentpath'});
+            $msg .= '</span>';
+            $msg .= '<br />';
+            $msg .= &mt('To upload, rename or delete existing [_1] in [_2].','<span class="LC_filename">'.$fname.'</span>', $port_path.$env{'form.currentpath'});
+            return ('file_exists',$msg);
+        }
+    }
+}
+
 
 =pod
 
@@ -7209,7 +7495,7 @@ Apache Request ref, $records is an array
 ######################################################
 sub csv_print_samples {
     my ($r,$records) = @_;
-    my $samples = &get_samples($records,3);
+    my $samples = &get_samples($records,5);
 
     $r->print(&mt('Samples').'<br />'.&start_data_table().
               &start_data_table_header_row());
@@ -7264,7 +7550,7 @@ sub csv_print_select_table {
 	foreach my $sample (sort({$a <=> $b} keys(%{ $samples->[0] }))) {
 	    $r->print('<option value="'.$sample.'"'.
                       ($sample eq $defaultcol ? ' selected="selected" ' : '').
-                      '>Column '.($sample+1).'</option>');
+                      '>'.&mt('Column [_1]',($sample+1)).'</option>');
 	}
 	$r->print('</select></td>'.&end_data_table_row()."\n");
 	$i++;
@@ -7295,7 +7581,8 @@ sub csv_samples_select_table {
     my ($r,$records,$d) = @_;
     my $i=0;
     #
-    my $samples = &get_samples($records,3);
+    my $max_samples = 5;
+    my $samples = &get_samples($records,$max_samples);
     $r->print(&start_data_table().
               &start_data_table_header_row().'<th>'.
               &mt('Field').'</th><th>'.&mt('Samples').'</th>'.
@@ -7311,7 +7598,7 @@ sub csv_samples_select_table {
                       $display.'</option>');
 	}
 	$r->print('</select></td><td>');
-	foreach my $line (0..2) {
+	foreach my $line (0..($max_samples-1)) {
 	    if (defined($samples->[$line]{$key})) { 
 		$r->print($samples->[$line]{$key}."<br />\n"); 
 	    }
@@ -7922,7 +8209,9 @@ defdom (domain for which to retrieve con
 origmail (scalar - email address of recipient from loncapa.conf, 
 i.e., predates configuration by DC via domainprefs.pm 
 
-Returns: comma separated list of addresses to which to send e-mail.   
+Returns: comma separated list of addresses to which to send e-mail.
+
+=back
 
 =cut
 
@@ -7973,13 +8262,344 @@ sub build_recipient_list {
 ############################################################
 ############################################################
 
+=pod
+
+=head1 Course Catalog Routines
+
+=over 4
+
+=item * &gather_categories()
+
+Converts category definitions - keys of categories hash stored in  
+coursecategories in configuration.db on the primary library server in a 
+domain - to an array.  Also generates javascript and idx hash used to 
+generate Domain Coordinator interface for editing Course Categories.
+
+Inputs:
+
+categories (reference to hash of category definitions).
+
+cats (reference to array of arrays/hashes which encapsulates hierarchy of
+      categories and subcategories).
+
+idx (reference to hash of counters used in Domain Coordinator interface for 
+      editing Course Categories).
+
+jsarray (reference to array of categories used to create Javascript arrays for
+         Domain Coordinator interface for editing Course Categories).
+
+Returns: nothing
+
+Side effects: populates cats, idx and jsarray. 
+
+=cut
+
+sub gather_categories {
+    my ($categories,$cats,$idx,$jsarray) = @_;
+    my %counters;
+    my $num = 0;
+    foreach my $item (keys(%{$categories})) {
+        my ($cat,$container,$depth) = map { &unescape($_); } split(/:/,$item);
+        if ($container eq '' && $depth == 0) {
+            $cats->[$depth][$categories->{$item}] = $cat;
+        } else {
+            $cats->[$depth]{$container}[$categories->{$item}] = $cat;
+        }
+        my ($escitem,$tail) = split(/:/,$item,2);
+        if ($counters{$tail} eq '') {
+            $counters{$tail} = $num;
+            $num ++;
+        }
+        if (ref($idx) eq 'HASH') {
+            $idx->{$item} = $counters{$tail};
+        }
+        if (ref($jsarray) eq 'ARRAY') {
+            push(@{$jsarray->[$counters{$tail}]},$item);
+        }
+    }
+    return;
+}
+
+=pod
+
+=item * &extract_categories()
+
+Used to generate breadcrumb trails for course categories.
+
+Inputs:
+
+categories (reference to hash of category definitions).
+
+cats (reference to array of arrays/hashes which encapsulates hierarchy of
+      categories and subcategories).
+
+trails (reference to array of breacrumb trails for each category).
+
+allitems (reference to hash - key is category key 
+         (format: escaped(name):escaped(parent category):depth in hierarchy).
+
+idx (reference to hash of counters used in Domain Coordinator interface for
+      editing Course Categories).
+
+jsarray (reference to array of categories used to create Javascript arrays for
+         Domain Coordinator interface for editing Course Categories).
+
+subcats (reference to hash of arrays containing all subcategories within each 
+         category, -recursive)
+
+Returns: nothing
+
+Side effects: populates trails and allitems hash references.
+
+=cut
+
+sub extract_categories {
+    my ($categories,$cats,$trails,$allitems,$idx,$jsarray,$subcats) = @_;
+    if (ref($categories) eq 'HASH') {
+        &gather_categories($categories,$cats,$idx,$jsarray);
+        if (ref($cats->[0]) eq 'ARRAY') {
+            for (my $i=0; $i<@{$cats->[0]}; $i++) {
+                my $name = $cats->[0][$i];
+                my $item = &escape($name).'::0';
+                my $trailstr;
+                if ($name eq 'instcode') {
+                    $trailstr = &mt('Official courses (with institutional codes)');
+                } else {
+                    $trailstr = $name;
+                }
+                if ($allitems->{$item} eq '') {
+                    push(@{$trails},$trailstr);
+                    $allitems->{$item} = scalar(@{$trails})-1;
+                }
+                my @parents = ($name);
+                if (ref($cats->[1]{$name}) eq 'ARRAY') {
+                    for (my $j=0; $j<@{$cats->[1]{$name}}; $j++) {
+                        my $category = $cats->[1]{$name}[$j];
+                        if (ref($subcats) eq 'HASH') {
+                            push(@{$subcats->{$item}},&escape($category).':'.&escape($name).':1');
+                        }
+                        &recurse_categories($cats,2,$category,$trails,$allitems,\@parents,$subcats);
+                    }
+                } else {
+                    if (ref($subcats) eq 'HASH') {
+                        $subcats->{$item} = [];
+                    }
+                }
+            }
+        }
+    }
+    return;
+}
+
+=pod
+
+=item *&recurse_categories()
+
+Recursively used to generate breadcrumb trails for course categories.
+
+Inputs:
+
+cats (reference to array of arrays/hashes which encapsulates hierarchy of
+      categories and subcategories).
+
+depth (current depth in hierarchy of categories and sub-categories - 0 indexed).
+
+category (current course category, for which breadcrumb trail is being generated).
+
+trails (reference to array of breadcrumb trails for each category).
+
+allitems (reference to hash - key is category key
+         (format: escaped(name):escaped(parent category):depth in hierarchy).
+
+parents (array containing containers directories for current category, 
+         back to top level). 
+
+Returns: nothing
+
+Side effects: populates trails and allitems hash references
+
+=cut
+
+sub recurse_categories {
+    my ($cats,$depth,$category,$trails,$allitems,$parents,$subcats) = @_;
+    my $shallower = $depth - 1;
+    if (ref($cats->[$depth]{$category}) eq 'ARRAY') {
+        for (my $k=0; $k<@{$cats->[$depth]{$category}}; $k++) {
+            my $name = $cats->[$depth]{$category}[$k];
+            my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
+            my $trailstr = join(' -&gt; ',(@{$parents},$category));
+            if ($allitems->{$item} eq '') {
+                push(@{$trails},$trailstr);
+                $allitems->{$item} = scalar(@{$trails})-1;
+            }
+            my $deeper = $depth+1;
+            push(@{$parents},$category);
+            if (ref($subcats) eq 'HASH') {
+                my $subcat = &escape($name).':'.$category.':'.$depth;
+                for (my $j=@{$parents}; $j>=0; $j--) {
+                    my $higher;
+                    if ($j > 0) {
+                        $higher = &escape($parents->[$j]).':'.
+                                  &escape($parents->[$j-1]).':'.$j;
+                    } else {
+                        $higher = &escape($parents->[$j]).'::'.$j;
+                    }
+                    push(@{$subcats->{$higher}},$subcat);
+                }
+            }
+            &recurse_categories($cats,$deeper,$name,$trails,$allitems,$parents,
+                                $subcats);
+            pop(@{$parents});
+        }
+    } else {
+        my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
+        my $trailstr = join(' -&gt; ',(@{$parents},$category));
+        if ($allitems->{$item} eq '') {
+            push(@{$trails},$trailstr);
+            $allitems->{$item} = scalar(@{$trails})-1;
+        }
+    }
+    return;
+}
+
+=pod
+
+=item *&assign_categories_table()
+
+Create a datatable for display of hierarchical categories in a domain,
+with checkboxes to allow a course to be categorized. 
+
+Inputs:
+
+cathash - reference to hash of categories defined for the domain (from
+          configuration.db)
+
+currcat - scalar with an & separated list of categories assigned to a course. 
+
+Returns: $output (markup to be displayed) 
+
+=cut
+
+sub assign_categories_table {
+    my ($cathash,$currcat) = @_;
+    my $output;
+    if (ref($cathash) eq 'HASH') {
+        my (@cats,@trails,%allitems,%idx,@jsarray,@path,$maxdepth);
+        &extract_categories($cathash,\@cats,\@trails,\%allitems,\%idx,\@jsarray);
+        $maxdepth = scalar(@cats);
+        if (@cats > 0) {
+            my $itemcount = 0;
+            if (ref($cats[0]) eq 'ARRAY') {
+                $output = &Apache::loncommon::start_data_table();
+                my @currcategories;
+                if ($currcat ne '') {
+                    @currcategories = split('&',$currcat);
+                }
+                for (my $i=0; $i<@{$cats[0]}; $i++) {
+                    my $parent = $cats[0][$i];
+                    my $css_class = $itemcount%2?' class="LC_odd_row"':'';
+                    next if ($parent eq 'instcode');
+                    my $item = &escape($parent).'::0';
+                    my $checked = '';
+                    if (@currcategories > 0) {
+                        if (grep(/^\Q$item\E$/,@currcategories)) {
+                            $checked = ' checked="checked" ';
+                        }
+                    }
+                    $output .= '<tr '.$css_class.'><td><span class="LC_nobreak">'
+                               .'<input type="checkbox" name="usecategory" value="'.
+                               $item.'"'.$checked.' />'.&escape($parent).'</span></td>';
+                    my $depth = 1;
+                    push(@path,$parent);
+                    $output .= &assign_category_rows($itemcount,\@cats,$depth,$parent,\@path,\@currcategories);
+                    pop(@path);
+                    $output .= '</tr><tr><td colspan="'.$maxdepth.'" class="LC_row_separator"></td></tr>';
+                    $itemcount ++;
+                }
+                $output .= &Apache::loncommon::end_data_table();
+            }
+        }
+    }
+    return $output;
+}
+
+=pod
+
+=item *&assign_category_rows()
+
+Create a datatable row for display of nested categories in a domain,
+with checkboxes to allow a course to be categorized,called recursively.
+
+Inputs:
+
+itemcount - track row number for alternating colors
+
+cats - reference to array of arrays/hashes which encapsulates hierarchy of
+      categories and subcategories.
+
+depth - current depth in hierarchy of categories and sub-categories - 0 indexed.
+
+parent - parent of current category item
+
+path - Array containing all categories back up through the hierarchy from the
+       current category to the top level.
+
+currcategories - reference to array of current categories assigned to the course
+
+Returns: $output (markup to be displayed).
+
+=cut
+
+sub assign_category_rows {
+    my ($itemcount,$cats,$depth,$parent,$path,$currcategories) = @_;
+    my ($text,$name,$item,$chgstr);
+    if (ref($cats) eq 'ARRAY') {
+        my $maxdepth = scalar(@{$cats});
+        if (ref($cats->[$depth]) eq 'HASH') {
+            if (ref($cats->[$depth]{$parent}) eq 'ARRAY') {
+                my $numchildren = @{$cats->[$depth]{$parent}};
+                my $css_class = $itemcount%2?' class="LC_odd_row"':'';
+                $text .= '<td><table class="LC_datatable">';
+                for (my $j=0; $j<$numchildren; $j++) {
+                    $name = $cats->[$depth]{$parent}[$j];
+                    $item = &escape($name).':'.&escape($parent).':'.$depth;
+                    my $deeper = $depth+1;
+                    my $checked = '';
+                    if (ref($currcategories) eq 'ARRAY') {
+                        if (@{$currcategories} > 0) {
+                            if (grep(/^\Q$item\E$/,@{$currcategories})) {
+                                $checked = ' checked="checked" ';
+                            }
+                        }
+                    }
+                    $text .= '<tr><td><span class="LC_nobreak"><label>'.
+                             '<input type="checkbox" name="usecategory" value="'.
+                             $item.'"'.$checked.' />'.$name.'</label></span></td><td>';
+                    if (ref($path) eq 'ARRAY') {
+                        push(@{$path},$name);
+                        $text .= &assign_category_rows($itemcount,$cats,$deeper,$name,$path,$currcategories);
+                        pop(@{$path});
+                    }
+                    $text .= '</td></tr>';
+                }
+                $text .= '</table></td>';
+            }
+        }
+    }
+    return $text;
+}
+
+############################################################
+############################################################
+
+
 sub commit_customrole {
-    my ($udom,$uname,$url,$three,$four,$five,$start,$end) = @_;
+    my ($udom,$uname,$url,$three,$four,$five,$start,$end,$context) = @_;
     my $output = &mt('Assigning custom role').' "'.$five.'" by '.$four.':'.$three.' in '.$url.
                          ($start?', '.&mt('starting').' '.localtime($start):'').
                          ($end?', ending '.localtime($end):'').': <b>'.
               &Apache::lonnet::assigncustomrole(
-                 $udom,$uname,$url,$three,$four,$five,$end,$start).
+                 $udom,$uname,$url,$three,$four,$five,$end,$start,undef,undef,$context).
                  '</b><br />';
     return $output;
 }
@@ -8014,7 +8634,7 @@ sub commit_standardrole {
         $output = &mt('Assigning').' '.$three.' in '.$url.
                ($start?', '.&mt('starting').' '.localtime($start):'').
                ($end?', '.&mt('ending').' '.localtime($end):'').': ';
-        my $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start);
+        my $result = &Apache::lonnet::assignrole($udom,$uname,$url,$three,$end,$start,'','',$context);
         if ($context eq 'auto') {
             $output .= $result.$linefeed;
         } else {
@@ -8049,7 +8669,7 @@ sub commit_studentrole {
                 }
                 $oldsecurl = $uurl;
                 $expire_role_result = 
-                    &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now);
+                    &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,'','',$context);
                 if ($env{'request.course.sec'} ne '') { 
                     if ($expire_role_result eq 'refused') {
                         my @roles = ('st');
@@ -8072,7 +8692,7 @@ sub commit_studentrole {
             }
         }
         if (($expire_role_result eq 'ok') || ($secchange == 0)) {
-            $modify_section_result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,'','',$cid);
+            $modify_section_result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,'','',$cid,'',$context);
             if ($modify_section_result =~ /^ok/) {
                 if ($secchange == 1) {
                     if ($sec eq '') {