--- loncom/interface/loncommon.pm 2002/06/25 16:31:51 1.40 +++ loncom/interface/loncommon.pm 2009/11/30 21:29:47 1.920 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.40 2002/06/25 16:31:51 ng Exp $ +# $Id: loncommon.pm,v 1.920 2009/11/30 21:29:47 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,14 +25,6 @@ # # http://www.lon-capa.org/ # -# YEAR=2001 -# 2/13-12/7 Guy Albertelli -# 12/11,12/12,12/17 Scott Harrison -# 12/21 Gerd Kortemeyer -# 12/21 Scott Harrison -# 12/25,12/28 Gerd Kortemeyer -# YEAR=2002 -# 1/4 Gerd Kortemeyer # Makes a table out of the previous attempts # Inputs result_from_symbread, user, domain, course_id @@ -40,236 +32,965 @@ # POD header: +=pod + =head1 NAME Apache::loncommon - pile of common routines =head1 SYNOPSIS -Referenced by other mod_perl Apache modules. - -Invocation: - &Apache::loncommon::SUBROUTINENAME(ARGUMENTS); +Common routines for manipulating connections, student answers, + domains, common Javascript fragments, etc. -=head1 INTRODUCTION +=head1 OVERVIEW -Common collection of used subroutines. This collection helps remove +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. -Current things done: - - Makes a table out of the previous homework attempts - Inputs result_from_symbread, user, domain, course_id - Reads in non-network-related .tab files - -This is part of the LearningOnline Network with CAPA project -described at http://www.lon-capa.org. - -=head2 Subroutines - -=over 4 - =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 Apache::lonnet(); -use POSIX qw(strftime); -use Apache::Constants qw(:common); -use Apache::lonmsg(); +use HTML::Entities; +use Apache::lonhtmlcommon(); +use Apache::loncoursedata(); +use Apache::lontexconvert(); +use Apache::lonclonecourse(); +use LONCAPA qw(:DEFAULT :match); +use DateTime::TimeZone; +use DateTime::Locale::Catalog; + +# ---------------------------------------------- Designs +use vars qw(%defaultdesign); my $readit; -# ----------------------------------------------- Filetypes/Languages/Copyright -my %language; -my %cprtag; -my %fe; my %fd; -my %fc; -# -------------------------------------------------------------- Thesaurus data -my @therelated; -my @theword; -my @thecount; -my %theindex; -my $thetotalcount; -my $thefuzzy=2; -my $thethreshold=0.1/$thefuzzy; -my $theavecount; +## +## Global Variables +## + + +# ----------------------------------------------- SSI with retries: +# + +=pod + +=head1 Server Side include with retries: + +=over 4 + +=item * &ssi_with_retries(resource,retries form) + +Performs an ssi with some number of retries. Retries continue either +until the result is ok or until the retry count supplied by the +caller is exhausted. + +Inputs: -# ----------------------------------------------------------------------- BEGIN -=item BEGIN() +=over 4 + +resource - Identifies the resource to insert. + +retries - Count of the number of retries allowed. + +form - Hash that identifies the rendering options. + +=back + +Returns: + +=over 4 + +content - The content of the response. If retries were exhausted this is empty. + +response - The response from the last attempt (which may or may not have been successful. + +=back -Initialize values from language.tab, copyright.tab, filetypes.tab, -and filecategories.tab. +=back =cut -# ----------------------------------------------------------------------- BEGIN -BEGIN { +sub ssi_with_retries { + my ($resource, $retries, %form) = @_; + + + my $ok = 0; # True if we got a good response. + my $content; + my $response; + + # Try to get the ssi done. within the retries count: + + do { + ($content, $response) = &Apache::lonnet::ssi($resource, %form); + $ok = $response->is_success; + if (!$ok) { + &Apache::lonnet::logthis("Failed ssi_with_retries on $resource: ".$response->is_success.', '.$response->code.', '.$response->message); + } + $retries--; + } while (!$ok && ($retries > 0)); + + if (!$ok) { + $content = ''; # On error return an empty content. + } + return ($content, $response); + +} + + +# ----------------------------------------------- Filetypes/Languages/Copyright +my %language; +my %supported_language; +my %cprtag; +my %scprtag; +my %fe; my %fd; my %fm; +my %category_extensions; + +# ---------------------------------------------- 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 $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. - '/language.tab'); - if ($fh) { - while (<$fh>) { - next if /^\#/; - chomp; - my ($key,$val)=(split(/\s+/,$_,2)); - $language{$key}=$val; - } - } + 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 $fh=Apache::File->new($Apache::lonnet::perlvar{'lonIncludes'}. - '/copyright.tab'); - if ($fh) { - while (<$fh>) { - next if /^\#/; - chomp; - my ($key,$val)=(split(/\s+/,$_,2)); - $cprtag{$key}=$val; - } - } + 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); + } } + +# -------------------------------------------------------------- default domain designs + my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors'; + my $designfile = $designdir.'/default.tab'; + if ( open (my $fh,"<$designfile") ) { + while (my $line = <$fh>) { + next if ($line =~ /^\#/); + chomp($line); + my ($key,$val)=(split(/\=/,$line)); + if ($val) { $defaultdesign{$key}=$val; } + } + close($fh); + } + # ------------------------------------------------------------- file categories { - my $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. - '/filecategories.tab'); - if ($fh) { - while (<$fh>) { - next if /^\#/; - chomp; - my ($key,$val)=(split(/\s+/,$_,2)); - push @{$fc{$key}},$val; - } - } + 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 $fh=Apache::File->new($Apache::lonnet::perlvar{'lonTabDir'}. - '/filetypes.tab'); - if ($fh) { - while (<$fh>) { - next if (/^\#/); - chomp; - my ($ending,$emb,$descr)=split(/\s+/,$_,3); - if ($descr ne '') { - $fe{$ending}=lc($emb); - $fd{$ending}=$descr; - } - } + 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 <new($Apache::lonnet::perlvar{'lonTabDir'}. - '/thesaurus.dat'); - if ($fh) { - while (<$fh>) { - my ($tword,$tindex,$tcount,$trelated)=split(/\@/,$_); - $theindex{$tword}=$tindex; - $theword[$tindex]=$tword; - $thecount[$tindex]=$tcount; - $thetotalcount+=$tcount; - $therelated[$tindex]=$trelated; + var editsearcher; + function opensearcher(formname,elementname,titleelement) { + var url = '/adm/searchcat?'; + if (editsearcher == null) { + url += 'launch=1&'; + } + url += 'catalogmode=interactive&'; + url += 'mode=$mode&'; + url += 'form=' + formname + '&'; + if (titleelement != null) { + url += 'titleelement=' + titleelement + '&'; + } else { + url += 'titleelement=&'; + } + url += 'element=' + elementname + ''; + var title = 'Search'; + var options = 'scrollbars=1,resizable=1,menubar=0,toolbar=1,location=1'; + options += ',width=700,height=600'; + editsearcher = open(url,title,options,'1'); + editsearcher.focus(); + } +// END LON-CAPA Internal --> +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,$courseadvonly)=@_; + my $callargs = "'".$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 ''; + } + if ($courseadvonly) { + $callargs .= ",'',1,1"; + } + return ''. + ''. + &mt('Select User').''; + } + if ($env{'request.role'}=~/^(au|dc|su)/) { + $callargs .= ",1"; + return ''. + ''. + &mt('Select User').''; + } + return ''; +} + +sub authorbrowser_javascript { + return <<"ENDAUTHORBRW"; + +ENDAUTHORBRW } -# ============================================================= END BEGIN BLOCK -=item linked_select_forms(...) +sub coursebrowser_javascript { + my ($domainfilter,$sec_element,$formname,$role_element,$crstype) = @_; + my $wintitle = &mt('Course Browser'); + if ($crstype ne '') { + $wintitle = &mt($crstype); + } + my $id_functions = &javascript_index_functions(); + my $output = ' +'; + return $output; +} + +sub javascript_index_functions { + return <<"ENDJS"; + +function getFormIdByName(formname) { + for (var i=0;i -1) { + var domid = getIndexByName(formid,udom); + if (domid > -1) { + if (document.forms[formid].elements[domid].type == 'select-one') { + userdom=document.forms[formid].elements[domid].options[document.forms[formid].elements[domid].selectedIndex].value; + } + if (document.forms[formid].elements[domid].type == 'hidden') { + userdom=document.forms[formid].elements[domid].value; + } + } + } + return userdom; +} + +ENDJS + +} + +sub userbrowser_javascript { + my $id_functions = &javascript_index_functions(); + return <<"ENDUSERBRW"; + +function openuserbrowser(formname,uname,udom,ulast,ufirst,uemail,hideudom,crsdom,caller) { + var url = '/adm/pickuser?'; + var userdom = getDomainFromSelectbox(formname,udom); + if (userdom != null) { + if (userdom != '') { + url += 'srchdom='+userdom+'&'; + } + } + url += 'form=' + formname + '&unameelement='+uname+ + '&udomelement='+udom+ + '&ulastelement='+ulast+ + '&ufirstelement='+ufirst+ + '&uemailelement='+uemail+ + '&hideudomelement='+hideudom+ + '&coursedom='+crsdom; + if ((caller != null) && (caller != undefined)) { + url += '&caller='+caller; + } + var title = 'User_Browser'; + var options = 'scrollbars=1,resizable=1,menubar=0'; + options += ',width=700,height=600'; + var stdeditbrowser = open(url,title,options,'1'); + stdeditbrowser.focus(); +} + +function fix_domain (formname,udom,origdom,uname) { + var formid = getFormIdByName(formname); + if (formid > -1) { + var unameid = getIndexByName(formid,uname); + var domid = getIndexByName(formid,udom); + var hidedomid = getIndexByName(formid,origdom); + if (hidedomid > -1) { + var fixeddom = document.forms[formid].elements[hidedomid].value; + var unameval = document.forms[formid].elements[unameid].value; + if ((fixeddom != '') && (fixeddom != undefined) && (fixeddom != null) && (unameval != '') && (unameval != undefined) && (unameval != null)) { + if (domid > -1) { + var slct = document.forms[formid].elements[domid]; + if (slct.type == 'select-one') { + var i; + for (i=0;i' + ."".$linktext.'' + .''; +} + +sub selectauthor_link { + my ($form,$udom)=@_; + return ''. + &mt('Select Author').''; +} + +sub selectuser_link { + my ($form,$unameelem,$domelem,$lastelem,$firstelem,$emailelem,$hdomelem, + $coursedom,$linktext,$caller) = @_; + return ''.$linktext.''; +} + +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; +} + +sub select_timezone { + my ($name,$selected,$onchange,$includeempty)=@_; + my $output=' menus. The select menus will be linked in that changing the value of the first menu will result in new values being placed in the second menu. The values in the select menu will appear in alphabetical -order. +order unless a defined order is provided. linked_select_forms takes the following ordered inputs: =over 4 -=item $formname, the name of the
tag +=item * $formname, the name of the tag -=item $middletext, the text which appears between the tags -=item $firstdefault, the default value for the first menu +=item * $firstdefault, the default value for the first menu -=item $firstselectname, the name of the first tag -=item $secondselectname, the name of the second tag -=item $hashref, a reference to a hash containing the data for the menus. +=item * $hashref, a reference to a hash containing the data for the menus. + +=item * $menuorder, the order of values in the first menu + +=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 values is given in $menu{$choice1}->{'text'}. The values +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" - } - } - ); - -=back + default => "B3", + select2 => { + B1 => "Choice B1", + B2 => "Choice B2", + B3 => "Choice B3", + B4 => "Choice B4" + }, + order => ['B4','B3','B1','B2'], + }, + A2 => { text =>"Choice A2" , + default => "C2", + select2 => { + C1 => "Choice C1", + C2 => "Choice C2", + C3 => "Choice C3" + }, + order => ['C2','C1','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" + }, + order => ['D4','D3','D2','D1','D7','D6','D5'], + } + ); =cut -# ------------------------------------------------ - sub linked_select_forms { my ($formname, $middletext, $firstdefault, $firstselectname, $secondselectname, - $hashref + $hashref, + $menuorder, ) = @_; 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); + $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.''; + } + + # (Always) Add the graphic + my $title = &mt('Online Help'); + my $helpicon=&lonhttpdurl("/adm/help/help.png"); + $template.=' ' + .''.&mt('Help: [_1]',$topic).''; + 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 ($topic,$text,$not_author) = @_; + my $out; + my $addOther = ''; + if ($topic) { + $addOther = ''.&Apache::loncommon::help_open_topic($topic,&mt($text), + undef, undef, 600). + ' '; + } + $out = '' # Start cheatsheet + .$addOther + .'' + .&Apache::loncommon::help_open_topic('Greek_Symbols',&mt('Greek Symbols'), + undef,undef,600) + .' ' + .&Apache::loncommon::help_open_topic('Other_Symbols',&mt('Other Symbols'), + undef,undef,600) + .''; + unless ($not_author) { + $out .= ' ' + .&Apache::loncommon::help_open_topic('Authoring_Output_Tags',&mt('Output Tags'), + undef,undef,600) + .''; + } + $out .= ''; # End cheatsheet + return $out; +} + +sub general_help { + my $helptopic='Student_Intro'; + if ($env{'request.role'}=~/^(ca|au)/) { + $helptopic='Authoring_Intro'; + } elsif ($env{'request.role'}=~/^(cc|co)/) { + $helptopic='Course_Coordination_Intro'; + } elsif ($env{'request.role'}=~/^dc/) { + $helptopic='Domain_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); + # only use pop-up help (stayOnPage == 0) + # if environment.remote is on (using remote control UI) + if ($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 $stay_on_page = + ($env{'environment.remote'} eq 'off' ); + my $link = ($stay_on_page) ? "javascript:helpMenu('display')" + : "javascript:helpMenu('open')"; + my $banner_link = &update_help_link(undef,undef,undef,undef,$stay_on_page); + + my $title = &mt('Get help'); + + return <<"END"; +$banner_link + $text +END +} + +sub help_menu_js { + my ($text) = @_; + + my $stayOnPage = + ($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' => "110,*",},}); + 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{'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{'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 + +=item * &viewport_geometry_js + +Provides javascript object (Geometry) which can provide information about the viewport geometry for the client browser. + +=cut + + +sub viewport_geometry_js { + return <<"GEOMETRY"; +var Geometry = {}; +function init_geometry() { + if (Geometry.init) { return }; + Geometry.init=1; + if (window.innerHeight) { + Geometry.getViewportHeight = function() { return window.innerHeight; }; + Geometry.getViewportWidth = function() { return window.innerWidth; }; + Geometry.getHorizontalScroll = function() { return window.pageXOffset; }; + Geometry.getVerticalScroll = function() { return window.pageYOffset; }; + } + else if (document.documentElement && document.documentElement.clientHeight) { + Geometry.getViewportHeight = + function() { return document.documentElement.clientHeight; }; + Geometry.getViewportWidth = + function() { return document.documentElement.clientWidth; }; + + Geometry.getHorizontalScroll = + function() { return document.documentElement.scrollLeft; }; + Geometry.getVerticalScroll = + function() { return document.documentElement.scrollTop; }; + } + else if (document.body.clientHeight) { + Geometry.getViewportHeight = + function() { return document.body.clientHeight; }; + Geometry.getViewportWidth = + function() { return document.body.clientWidth; }; + Geometry.getHorizontalScroll = + function() { return document.body.scrollLeft; }; + Geometry.getVerticalScroll = + function() { return document.body.scrollTop; }; + } +} + +GEOMETRY +} + +=pod + +=item * &viewport_size_js() + +Provides a javascript function to set values of two form elements - width and height (elements are passed in as arguments to the javascript function) to the dimensions of the user's browser window. + +=cut + +sub viewport_size_js { + my $geometry = &viewport_geometry_js(); + return <<"DIMS"; + +$geometry + +function getViewportDims(width,height) { + init_geometry(); + width.value = Geometry.getViewportWidth(); + height.value = Geometry.getViewportHeight(); + return; +} + +DIMS +} + +=pod + +=item * &resize_textarea_js() + +emits the needed javascript to resize a textarea to be as big as possible + +creates a function resize_textrea that takes two IDs first should be +the id of the element to resize, second should be the id of a div that +surrounds everything that comes after the textarea, this routine needs +to be attached to the for the onload and onresize events. + +=back + +=cut + +sub resize_textarea_js { + my $geometry = &viewport_geometry_js(); + return <<"RESIZE"; + +RESIZE + +} + +=pod + +=head1 Excel and CSV file utility routines + +=over 4 + +=cut + +############################################################### ############################################################### -=item csv_translate($text) +=pod -Translate $text to allow it to be output as a 'comma seperated values' +=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; + $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 user. +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(&mt('Problems occurred 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; } #------------------------------------------- -=item select_dom_form($defdom,$name) +=pod + +=item * &select_dom_form($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms) Returns a string containing a \n"; - foreach (@domains) { - $selectdomain.="\n"; + my ($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms) = @_; + if ($onchange) { + $onchange = ' onchange="'.$onchange.'"'; + } + my @domains; + if (ref($incdoms) eq 'ARRAY') { + @domains = sort {lc($a) cmp lc($b)} (@{$incdoms}); + } else { + @domains = sort {lc($a) cmp lc($b)} (&Apache::lonnet::all_domains()); + } + if ($includeempty) { @domains=('',@domains); } + my $selectdomain = ""; return $selectdomain; @@ -402,49 +1992,151 @@ sub select_dom_form { #------------------------------------------- -=item get_home_servers($domain) +=pod -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. +=item * &home_server_form_item($domain,$name,$defaultflag) + +input: 4 arguments (two required, two optional) - + $domain - domain of new user + $name - name of form element + $default - Value of 'default' causes a default item to be first + option, and selected by default. + $hide - Value of 'hide' causes hiding of the name of the server, + if 1 server found, or default, if 0 found. +output: returns 2 items: +(a) form element which contains either: + (i) + form item if there are multiple library servers in $domain, or + (ii) an form item + if there is only one library server in $domain. + +(b) number of library servers found. + +See loncreateuser.pm for example of use. =cut #------------------------------------------- -sub get_home_servers { - my $domain = shift; - my %home_servers; - foreach (keys(%Apache::lonnet::libserv)) { - if ($Apache::lonnet::hostdom{$_} eq $domain) { - $home_servers{$_} = $Apache::lonnet::hostname{$_}; +sub home_server_form_item { + my ($domain,$name,$default,$hide) = @_; + my %servers = &Apache::lonnet::get_servers($domain,'library'); + my $result; + my $numlib = keys(%servers); + if ($numlib > 1) { + $result .= ''."\n"; + } elsif ($numlib == 1) { + my $hostid; + foreach my $item (keys(%servers)) { + $hostid = $item; + } + $result .= ''; + if (!$hide) { + $result .= $hostid.' '.$servers{$hostid}; + } + $result .= "\n"; + } elsif ($default) { + $result .= ''; + if (!$hide) { + $result .= &mt('default'); + } + $result .= "\n"; } - return %home_servers; + return ($result,$numlib); } -#------------------------------------------- +=pod + +=back + +=cut + +############################################################### +## Decoding User Agent ## +############################################################### + +=pod + +=head1 Decoding the User Agent + +=over 4 + +=item * &decode_user_agent() + +Inputs: $r + +Outputs: + +=over 4 + +=item * $httpbrowser + +=item * $clientbrowser + +=item * $clientversion -=item home_server_option_list($domain) +=item * $clientmathml -returns a string which contains an