# The LearningOnline Network with CAPA # a pile of common routines # # $Id: loncommon.pm,v 1.505 2007/01/29 20:17:55 albertel Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # # Makes a table out of the previous attempts # Inputs result_from_symbread, user, domain, course_id # Reads in non-network-related .tab files # POD header: =pod =head1 NAME Apache::loncommon - pile of common routines =head1 SYNOPSIS Common routines for manipulating connections, student answers, domains, common Javascript fragments, etc. =head1 OVERVIEW A collection of commonly used subroutines that don't have a natural home anywhere else. This collection helps remove redundancy from other modules and increase efficiency of memory usage. =cut # End of POD header package Apache::loncommon; use strict; use Apache::lonnet; use GDBM_File; use POSIX qw(strftime mktime); use Apache::lonmenu(); use Apache::lonenc(); use Apache::lonlocal; use HTML::Entities; use Apache::lonhtmlcommon(); use Apache::loncoursedata(); use Apache::lontexconvert(); use Apache::lonclonecourse(); use LONCAPA qw(:DEFAULT :match); my $readit; ## ## Global Variables ## # ----------------------------------------------- Filetypes/Languages/Copyright my %language; my %supported_language; my %cprtag; my %scprtag; my %fe; my %fd; my %fm; my %category_extensions; # ---------------------------------------------- Designs my %designhash; # ---------------------------------------------- Thesaurus variables # # %Keywords: # A hash used by &keyword to determine if a word is considered a keyword. # $thesaurus_db_file # Scalar containing the full path to the thesaurus database. my %Keywords; my $thesaurus_db_file; # # Initialize values from language.tab, copyright.tab, filetypes.tab, # thesaurus.tab, and filecategories.tab. # BEGIN { # Variable initialization $thesaurus_db_file = $Apache::lonnet::perlvar{'lonTabDir'}."/thesaurus.db"; # unless ($readit) { # ------------------------------------------------------------------- languages { my $langtabfile = $Apache::lonnet::perlvar{'lonTabDir'}. '/language.tab'; if ( open(my $fh,"<$langtabfile") ) { while (my $line = <$fh>) { next if ($line=~/^\#/); chomp($line); my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$line)); $language{$key}=$val.' - '.$enc; if ($sup) { $supported_language{$key}=$sup; } } close($fh); } } # ------------------------------------------------------------------ copyrights { my $copyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}. '/copyright.tab'; if ( open (my $fh,"<$copyrightfile") ) { while (my $line = <$fh>) { next if ($line=~/^\#/); chomp($line); my ($key,$val)=(split(/\s+/,$line,2)); $cprtag{$key}=$val; } close($fh); } } # ----------------------------------------------------------- source copyrights { my $sourcecopyrightfile = $Apache::lonnet::perlvar{'lonIncludes'}. '/source_copyright.tab'; if ( open (my $fh,"<$sourcecopyrightfile") ) { while (my $line = <$fh>) { next if ($line =~ /^\#/); chomp($line); my ($key,$val)=(split(/\s+/,$line,2)); $scprtag{$key}=$val; } close($fh); } } # -------------------------------------------------------------- domain designs my $filename; my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors'; opendir(DIR,$designdir); while ($filename=readdir(DIR)) { if ($filename!~/\.tab$/) { next; } my ($domain)=($filename=~/^($match_domain)\./); { my $designfile = $designdir.'/'.$filename; if ( open (my $fh,"<$designfile") ) { while (my $line = <$fh>) { next if ($line =~ /^\#/); chomp($line); my ($key,$val)=(split(/\=/,$line)); if ($val) { $designhash{$domain.'.'.$key}=$val; } } close($fh); } } } closedir(DIR); # ------------------------------------------------------------- file categories { my $categoryfile = $Apache::lonnet::perlvar{'lonTabDir'}. '/filecategories.tab'; if ( open (my $fh,"<$categoryfile") ) { while (my $line = <$fh>) { next if ($line =~ /^\#/); chomp($line); my ($extension,$category)=(split(/\s+/,$line,2)); push @{$category_extensions{lc($category)}},$extension; } close($fh); } } # ------------------------------------------------------------------ file types { my $typesfile = $Apache::lonnet::perlvar{'lonTabDir'}. '/filetypes.tab'; if ( open (my $fh,"<$typesfile") ) { while (my $line = <$fh>) { next if ($line =~ /^\#/); chomp($line); my ($ending,$emb,$mime,$descr)=split(/\s+/,$line,4); if ($descr ne '') { $fe{$ending}=lc($emb); $fd{$ending}=$descr; if ($mime ne 'unk') { $fm{$ending}=$mime; } } } close($fh); } } &Apache::lonnet::logthis( "INFO: Read file types"); $readit=1; } # end of unless($readit) } ############################################################### ## HTML and Javascript Helper Functions ## ############################################################### =pod =head1 HTML and Javascript Functions =over 4 =item * browser_and_searcher_javascript () XXReturns a string containing javascript with two functions, C and C. Returned string does not contain EscriptE tags. =item * openbrowser(formname,elementname,only,omit) [javascript] inputs: formname, elementname, only, omit formname and elementname indicate the name of the html form and name of the element that the results of the browsing selection are to be placed in. Specifying 'only' will restrict the browser to displaying only files with the given extension. Can be a comma separated list. Specifying 'omit' will restrict the browser to NOT displaying files with the given extension. Can be a comma separated list. =item * opensearcher(formname, elementname) [javascript] Inputs: formname, elementname formname and elementname specify the name of the html form and the name of the element the selection from the search results will be placed in. =cut sub browser_and_searcher_javascript { my ($mode)=@_; if (!defined($mode)) { $mode='edit'; } my $resurl=&escape_single(&lastresurl()); return < END } sub lastresurl { if ($env{'environment.lastresurl'}) { return $env{'environment.lastresurl'} } else { return '/res'; } } sub storeresurl { my $resurl=&Apache::lonnet::clutter(shift); unless ($resurl=~/^\/res/) { return 0; } $resurl=~s/\/$//; &Apache::lonnet::put('environment',{'lastresurl' => $resurl}); &Apache::lonnet::appenv('environment.lastresurl' => $resurl); return 1; } sub studentbrowser_javascript { unless ( (($env{'request.course.id'}) && (&Apache::lonnet::allowed('srm',$env{'request.course.id'}) || &Apache::lonnet::allowed('srm',$env{'request.course.id'}. '/'.$env{'request.course.sec'}) )) || ($env{'request.role'}=~/^(au|dc|su)/) ) { return ''; } return (<<'ENDSTDBRW'); ENDSTDBRW } sub selectstudent_link { my ($form,$unameele,$udomele)=@_; if ($env{'request.course.id'}) { if (!&Apache::lonnet::allowed('srm',$env{'request.course.id'}) && !&Apache::lonnet::allowed('srm',$env{'request.course.id'}. '/'.$env{'request.course.sec'})) { return ''; } return "".&mt('Select User').""; } if ($env{'request.role'}=~/^(au|dc|su)/) { return "".&mt('Select User').""; } return ''; } 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'); my $output = ' '; return $output; } sub setsec_javascript { my ($sec_element,$formname) = @_; my $setsections = qq| function setSect(sectionlist) { var sectionsArray = sectionlist.split(","); var numSections = sectionsArray.length; document.$formname.$sec_element.length = 0; if (numSections == 0) { document.$formname.$sec_element.multiple=false; document.$formname.$sec_element.size=1; document.$formname.$sec_element.options[0] = new Option('No existing sections','',false,false) } else { if (numSections == 1) { document.$formname.$sec_element.multiple=false; document.$formname.$sec_element.size=1; document.$formname.$sec_element.options[0] = new Option('Select','',true,true); document.$formname.$sec_element.options[1] = new Option('No section','',false,false) document.$formname.$sec_element.options[2] = new Option(sectionsArray[0],sectionsArray[0],false,false); } else { for (var i=0; i".&mt('Select Course').""; } sub check_uncheck_jscript { my $jscript = <<"ENDSCRT"; function checkAll(field) { if (field.length > 0) { for (i = 0; i < field.length; i++) { field[i].checked = true ; } } else { field.checked = true } } function uncheckAll(field) { if (field.length > 0) { for (i = 0; i < field.length; i++) { field[i].checked = false ; } } else { field.checked = false ; } } ENDSCRT return $jscript; } =pod =item * linked_select_forms(...) linked_select_forms returns a string containing a block and html for two tags =item * $firstdefault, the default value for the first menu =item * $firstselectname, the name of the first tag =item * $hashref, a reference to a hash containing the data for the menus. =back Below is an example of such a hash. Only the 'text', 'default', and 'select2' keys must appear as stated. keys(%menu) are the possible values for the first select menu. The text that coincides with the first menu value is given in $menu{$choice1}->{'text'}. The values and text for the second menu are given in the hash pointed to by $menu{$choice1}->{'select2'}. my %menu = ( A1 => { text =>"Choice A1" , default => "B3", select2 => { B1 => "Choice B1", B2 => "Choice B2", B3 => "Choice B3", B4 => "Choice B4" } }, A2 => { text =>"Choice A2" , default => "C2", select2 => { C1 => "Choice C1", C2 => "Choice C2", C3 => "Choice C3" } }, A3 => { text =>"Choice A3" , default => "D6", select2 => { D1 => "Choice D1", D2 => "Choice D2", D3 => "Choice D3", D4 => "Choice D4", D5 => "Choice D5", D6 => "Choice D6", D7 => "Choice D7" } } ); =cut sub linked_select_forms { my ($formname, $middletext, $firstdefault, $firstselectname, $secondselectname, $hashref ) = @_; my $second = "document.$formname.$secondselectname"; my $first = "document.$formname.$firstselectname"; # output the javascript to do the changing my $result = ''; $result.=" END # output the initial values for the selection lists $result .= "\n"; my %select2 = %{$hashref->{$firstdefault}->{'select2'}}; $result .= $middletext; $result .= "\n"; # return $debug; return $result; } # end of sub linked_select_forms { =pod =item * help_open_topic($topic, $text, $stayOnPage, $width, $height) Returns a string corresponding to an HTML link to the given help $topic, where $topic corresponds to the name of a .tex file in /home/httpd/html/adm/help/tex, with underscores replaced by spaces. $text will optionally be linked to the same topic, allowing you to link text in addition to the graphic. If you do not want to link text, but wish to specify one of the later parameters, pass an empty string. $stayOnPage is a value that will be interpreted as a boolean. If true, the link will not open a new window. If false, the link will open a new window using Javascript. (Default is false.) $width and $height are optional numerical parameters that will override the width and height of the popped up window, which may be useful for certain help topics with big pictures included. =cut sub help_open_topic { my ($topic, $text, $stayOnPage, $width, $height) = @_; $text = "" if (not defined $text); $stayOnPage = 0 if (not defined $stayOnPage); if ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ) { $stayOnPage=1; } $width = 350 if (not defined $width); $height = 400 if (not defined $height); my $filename = $topic; $filename =~ s/ /_/g; my $template = ""; my $link; $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'))"; } else { $link = "/adm/help/${filename}.hlp"; } # Add the text if ($text ne "") { $template .= "". "
$text"; } # Add the graphic my $title = &mt('Online Help'); my $helpicon=&lonhttpdurl("/adm/help/gif/smallHelp.gif"); $template .= <<"ENDTEMPLATE"; (Help: $topic) ENDTEMPLATE if ($text ne '') { $template.='
' }; return $template; } # This is a quicky function for Latex cheatsheet editing, since it # appears in at least four places sub helpLatexCheatsheet { my $other = shift; my $addOther = ''; if ($other) { $addOther = Apache::loncommon::help_open_topic($other, shift, undef, undef, 600) . ''; } return '
'. $addOther . &Apache::loncommon::help_open_topic("Greek_Symbols",'Greek Symbols', undef,undef,600) .''. &Apache::loncommon::help_open_topic("Other_Symbols",'Other Symbols', undef,undef,600) .'
'; } sub general_help { my $helptopic='Student_Intro'; if ($env{'request.role'}=~/^(ca|au)/) { $helptopic='Authoring_Intro'; } elsif ($env{'request.role'}=~/^cc/) { $helptopic='Course_Coordination_Intro'; } return $helptopic; } sub update_help_link { my ($topic,$component_help,$faq,$bug,$stayOnPage) = @_; my $origurl = $ENV{'REQUEST_URI'}; $origurl=~s|^/~|/priv/|; my $timestamp = time; foreach my $datum (\$topic,\$component_help,\$faq,\$bug,\$origurl) { $$datum = &escape($$datum); } my $banner_link = "/adm/helpmenu?page=banner&topic=$topic&component_help=$component_help&faq=$faq&bug=$bug&origurl=$origurl&stamp=$timestamp&stayonpage=$stayOnPage"; my $output .= <<"ENDOUTPUT"; ENDOUTPUT return $output; } # now just updates the help link and generates a blue icon sub help_open_menu { my ($topic,$component_help,$faq,$bug,$stayOnPage,$width,$height,$text) = @_; $stayOnPage = 0 if (not defined $stayOnPage); if ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ) { $stayOnPage=1; } my $output; if ($component_help) { if (!$text) { $output=&help_open_topic($component_help,undef,$stayOnPage, $width,$height); } else { my $help_text; $help_text=&unescape($topic); $output='
'. &help_open_topic($component_help,$help_text,$stayOnPage, $width,$height).'
'; } } my $banner_link = &update_help_link($topic,$component_help,$faq,$bug,$stayOnPage); return $output.$banner_link; } sub top_nav_help { my ($text) = @_; $text = &mt($text); my $stayOnPage = ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ); my $link= ($stayOnPage) ? "javascript:helpMenu('display')" : "javascript:helpMenu('open')"; my $banner_link = &update_help_link(undef,undef,undef,undef,$stayOnPage); my $title = &mt('Get help'); return <<"END"; $banner_link $text END } sub help_menu_js { my ($text) = @_; my $stayOnPage = ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ); my $width = 620; my $height = 600; my $helptopic=&general_help(); my $details_link = '/adm/help/'.$helptopic.'.hlp'; my $nothing=&Apache::lonhtmlcommon::javascript_nothing(); my $start_page = &Apache::loncommon::start_page('Help Menu', undef, {'frameset' => 1, 'js_ready' => 1, 'add_entries' => { 'border' => '0', 'rows' => "105,*",},}); my $end_page = &Apache::loncommon::end_page({'frameset' => 1, 'js_ready' => 1,}); my $template .= <<"ENDTEMPLATE"; ENDTEMPLATE return $template; } sub help_open_bug { my ($topic, $text, $stayOnPage, $width, $height) = @_; unless ($env{'user.adv'}) { return ''; } unless ($Apache::lonnet::perlvar{'BugzillaHost'}) { return ''; } $text = "" if (not defined $text); $stayOnPage = 0 if (not defined $stayOnPage); if ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ) { $stayOnPage=1; } $width = 600 if (not defined $width); $height = 600 if (not defined $height); $topic=~s/\W+/\+/g; my $link=''; my $template=''; my $url=$Apache::lonnet::perlvar{'BugzillaHost'}.'enter_bug.cgi?product=LON-CAPA&bug_file_loc='. &escape($ENV{'REQUEST_URI'}).'&component='.$topic; if (!$stayOnPage) { $link = "javascript:void(open('$url', 'Bugzilla', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))"; } else { $link = $url; } # Add the text if ($text ne "") { $template .= "". "
$text"; } # Add the graphic my $title = &mt('Report a Bug'); my $bugicon=&lonhttpdurl("/adm/lonMisc/smallBug.gif"); $template .= <<"ENDTEMPLATE"; (Bug: $topic) ENDTEMPLATE if ($text ne '') { $template.='
' }; return $template; } sub help_open_faq { my ($topic, $text, $stayOnPage, $width, $height) = @_; unless ($env{'user.adv'}) { return ''; } unless ($Apache::lonnet::perlvar{'FAQHost'}) { return ''; } $text = "" if (not defined $text); $stayOnPage = 0 if (not defined $stayOnPage); if ($env{'browser.interface'} eq 'textual' || $env{'environment.remote'} eq 'off' ) { $stayOnPage=1; } $width = 350 if (not defined $width); $height = 400 if (not defined $height); $topic=~s/\W+/\+/g; my $link=''; my $template=''; my $url=$Apache::lonnet::perlvar{'FAQHost'}.'/fom/cache/'.$topic.'.html'; if (!$stayOnPage) { $link = "javascript:void(open('$url', 'FAQ-O-Matic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))"; } else { $link = $url; } # Add the text if ($text ne "") { $template .= "". "
$text"; } # Add the graphic my $title = &mt('View the FAQ'); my $faqicon=&lonhttpdurl("/adm/lonMisc/smallFAQ.gif"); $template .= <<"ENDTEMPLATE"; (FAQ: $topic) ENDTEMPLATE if ($text ne '') { $template.='
' }; return $template; } ############################################################### ############################################################### =pod =item * change_content_javascript(): This and the next function allow you to create small sections of an otherwise static HTML page that you can update on the fly with Javascript, even in Netscape 4. The Javascript fragment returned by this function (no EscriptE tag) must be written to the HTML page once. It will prove the Javascript function "change(name, content)". Calling the change function with the name of the section you want to update, matching the name passed to C, and the new content you want to put in there, will put the content into that area. B: Netscape 4 only reserves enough space for the changable area to contain room for the original contents. You need to "make space" for whatever changes you wish to make, and be B to check your code in Netscape 4. This feature in Netscape 4 is B powerful; it's adequate for updating a one-line status display, but little more. This script will set the space to 100% width, so you only need to worry about height in Netscape 4. Modern browsers are much less limiting, and if you can commit to the user not using Netscape 4, this feature may be used freely with pretty much any HTML. =cut sub change_content_javascript { # If we're on Netscape 4, we need to use Layer-based code if ($env{'browser.type'} eq 'netscape' && $env{'browser.version'} =~ /^4\./) { return (<. $name is the name you will use to reference the area later; do not repeat the same name on a given HTML page more then once. $origContent is what the area will originally contain, which can be left blank. =cut sub changable_area { my ($name, $origContent) = @_; if ($env{'browser.type'} eq 'netscape' && $env{'browser.version'} =~ /^4\./) { # If this is netscape 4, we need to use the Layer tag return "$origContent"; } else { return "$origContent"; } } =pod =back =head1 Excel and CSV file utility routines =over 4 =cut ############################################################### ############################################################### =pod =item * csv_translate($text) Translate $text to allow it to be output as a 'comma separated values' format. =cut ############################################################### ############################################################### sub csv_translate { my $text = shift; $text =~ s/\"/\"\"/g; $text =~ s/\n/ /g; return $text; } ############################################################### ############################################################### =pod =item * define_excel_formats Define some commonly used Excel cell formats. Currently supported formats: =over 4 =item header =item bold =item h1 =item h2 =item h3 =item h4 =item i =item date =back Inputs: $workbook Returns: $format, a hash reference. =cut ############################################################### ############################################################### sub define_excel_formats { my ($workbook) = @_; my $format; $format->{'header'} = $workbook->add_format(bold => 1, bottom => 1, align => 'center'); $format->{'bold'} = $workbook->add_format(bold=>1); $format->{'h1'} = $workbook->add_format(bold=>1, size=>18); $format->{'h2'} = $workbook->add_format(bold=>1, size=>16); $format->{'h3'} = $workbook->add_format(bold=>1, size=>14); $format->{'h4'} = $workbook->add_format(bold=>1, size=>12); $format->{'i'} = $workbook->add_format(italic=>1); $format->{'date'} = $workbook->add_format(num_format=> 'mm/dd/yyyy hh:mm:ss'); return $format; } ############################################################### ############################################################### =pod =item * create_workbook Create an Excel worksheet. If it fails, output message on the request object and return undefs. Inputs: Apache request object Returns (undef) on failure, Excel worksheet object, scalar with filename, and formats from &Apache::loncommon::define_excel_formats on success =cut ############################################################### ############################################################### sub create_workbook { my ($r) = @_; # # Create the excel spreadsheet my $filename = '/prtspool/'. $env{'user.name'}.'_'.$env{'user.domain'}.'_'. time.'_'.rand(1000000000).'.xls'; my $workbook = Spreadsheet::WriteExcel->new('/home/httpd'.$filename); if (! defined($workbook)) { $r->log_error("Error creating excel spreadsheet $filename: $!"); $r->print('

'.&mt("Unable to create new Excel file. ". "This error has been logged. ". "Please alert your LON-CAPA administrator"). '

'); return (undef); } # $workbook->set_tempdir('/home/httpd/perl/tmp'); # my $format = &Apache::loncommon::define_excel_formats($workbook); return ($workbook,$filename,$format); } ############################################################### ############################################################### =pod =item * create_text_file Create a file to write to and eventually make available to the usre. If file creation fails, outputs an error message on the request object and return undefs. Inputs: Apache request object, and file suffix Returns (undef) on failure, Filehandle and filename on success. =cut ############################################################### ############################################################### sub create_text_file { my ($r,$suffix) = @_; if (! defined($suffix)) { $suffix = 'txt'; }; my $fh; my $filename = '/prtspool/'. $env{'user.name'}.'_'.$env{'user.domain'}.'_'. time.'_'.rand(1000000000).'.'.$suffix; $fh = Apache::File->new('>/home/httpd'.$filename); if (! defined($fh)) { $r->log_error("Couldn't open $filename for output $!"); $r->print("Problems occured in creating the output file. ". "This error has been logged. ". "Please alert your LON-CAPA administrator."); } return ($fh,$filename) } =pod =back =cut ############################################################### ## Home server \n"; } $selectform.=""; return $selectform; } # For display filters sub display_filter { if (!$env{'form.show'}) { $env{'form.show'}=10; } if (!$env{'form.displayfilter'}) { $env{'form.displayfilter'}='currentfolder'; } return ' '. &mt('Filter [_1]', &select_form($env{'form.displayfilter'}, 'displayfilter', ('currentfolder' => 'Current folder/page', 'containing' => 'Containing phrase', 'none' => 'None'))). ''; } sub gradeleveldescription { my $gradelevel=shift; my %gradelevels=(0 => 'Not specified', 1 => 'Grade 1', 2 => 'Grade 2', 3 => 'Grade 3', 4 => 'Grade 4', 5 => 'Grade 5', 6 => 'Grade 6', 7 => 'Grade 7', 8 => 'Grade 8', 9 => 'Grade 9', 10 => 'Grade 10', 11 => 'Grade 11', 12 => 'Grade 12', 13 => 'Grade 13', 14 => '100 Level', 15 => '200 Level', 16 => '300 Level', 17 => '400 Level', 18 => 'Graduate Level'); return &mt($gradelevels{$gradelevel}); } sub select_level_form { my ($deflevel,$name)=@_; unless ($deflevel) { $deflevel=0; } my $selectform = ""; return $selectform; } #------------------------------------------- =pod =item * select_dom_form($defdom,$name,$includeempty) Returns a string containing a \n"; foreach my $dom (@domains) { $selectdomain.="\n"; } $selectdomain.=""; return $selectdomain; } #------------------------------------------- =pod =item * get_library_servers($domain) Returns a hash which contains keys like '103l3' and values like 'kirk.lite.msu.edu'. All of the keys will be for machines in the given $domain. =cut #------------------------------------------- sub get_library_servers { my $domain = shift; my %library_servers; foreach my $hostid (keys(%Apache::lonnet::libserv)) { if ($Apache::lonnet::hostdom{$hostid} eq $domain) { $library_servers{$hostid} = $Apache::lonnet::hostname{$hostid}; } } return %library_servers; } #------------------------------------------- =pod =item * home_server_option_list($domain) returns a string which contains an