--- loncom/interface/loncommon.pm 2002/04/15 23:37:37 1.31 +++ loncom/interface/loncommon.pm 2017/07/10 12:48:41 1.1284 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.31 2002/04/15 23:37:37 albertel Exp $ +# $Id: loncommon.pm,v 1.1284 2017/07/10 12:48:41 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -25,241 +25,4340 @@ # # 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 # 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 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 Apache::lonuserutils(); +use Apache::lonuserstate(); +use Apache::courseclassifier(); +use LONCAPA qw(:DEFAULT :match); +use LONCAPA::LWPReq; +use DateTime::TimeZone; +use DateTime::Locale; +use Encode(); +use Text::Aspell; +use Authen::Captcha; +use Captcha::reCAPTCHA; +use JSON::DWIW; +use LWP::UserAgent; +use Crypt::DES; +use DynaLoader; # for Crypt::DES version +use MIME::Lite; +use MIME::Types; + +# ---------------------------------------------- Designs +use vars qw(%defaultdesign); my $readit; + +## +## 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: + +=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 + +=back + +=cut + +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 %supported_codes; +my %latex_language; # For choosing hyphenation in +my %latex_language_bykey; # for choosing hyphenation from metadata my %cprtag; -my %fe; my %fd; -my %fc; +my %scprtag; +my %fe; my %fd; my %fm; +my %category_extensions; -# -------------------------------------------------------------- Thesaurus data -my @therelated; -my @theword; -my @thecount; -my %theindex; -my $thetotalcount; -my $thefuzzy=2; -my $thethreshold=0.1/$thefuzzy; -my $theavecount; +# ---------------------------------------------- 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. -# ----------------------------------------------------------------------- BEGIN -BEGIN { +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,$code,$country,$three,$enc,$val,$sup,$latex)=(split(/\t/,$line)); + $language{$key}=$val.' - '.$enc; + if ($sup) { + $supported_language{$key}=$sup; + $supported_codes{$key} = $code; + } + if ($latex) { + $latex_language_bykey{$key} = $latex; + $latex_language{$code} = $latex; + } + } + 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 resourcebrowser_javascript { + unless ($env{'request.course.id'}) { return ''; } + return (<<'ENDRESBRW'); + +ENDRESBRW } -# ============================================================= END BEGIN BLOCK +sub selectstudent_link { + my ($form,$unameele,$udomele,$courseadvonly,$clickerid)=@_; + my $callargs = "'".&Apache::lonhtmlcommon::entity_encode($form)."','". + &Apache::lonhtmlcommon::entity_encode($unameele)."','". + &Apache::lonhtmlcommon::entity_encode($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 ''; + } + $callargs.=",'".&Apache::lonhtmlcommon::entity_encode($clickerid)."'"; + if ($courseadvonly) { + $callargs .= ",'',1,1"; + } + return ''. + ''. + &mt('Select User').''; + } + if ($env{'request.role'}=~/^(au|dc|su)/) { + $callargs .= ",'',1"; + return ''. + ''. + &mt('Select User').''; + } + return ''; +} -# ---------------------------------------------------------- Is this a keyword? +sub selectresource_link { + my ($form,$reslink,$arg)=@_; + + my $callargs = "'".&Apache::lonhtmlcommon::entity_encode($form)."','". + &Apache::lonhtmlcommon::entity_encode($reslink)."'"; + unless ($env{'request.course.id'}) { return $arg; } + return ''. + ''. + $arg.''; +} -sub keyword { - my $newword=shift; - $newword=~s/\W//g; - $newword=~tr/A-Z/a-z/; - my $tindex=$theindex{$newword}; - if ($tindex) { - if ($thecount[$tindex]>$theavecount) { - return 1; - } - } - return 0; -} -# -------------------------------------------------------- Return related words - -sub related { - my $newword=shift; - $newword=~s/\W//g; - $newword=~tr/A-Z/a-z/; - my $tindex=$theindex{$newword}; - if ($tindex) { - my %found=(); - foreach (split(/\,/,$therelated[$tindex])) { -# - Related word found - my ($ridx,$rcount)=split(/\:/,$_); -# - Direct relation index - my $directrel=$rcount/$thecount[$tindex]; - if ($directrel>$thethreshold) { - foreach (split(/\,/,$therelated[$ridx])) { - my ($rridx,$rrcount)=split(/\:/,$_); - if ($rridx==$tindex) { -# - Determine reverse relation index - my $revrel=$rrcount/$thecount[$ridx]; -# - Calculate full index - $found{$ridx}=$directrel*$revrel; - if ($found{$ridx}>$thethreshold) { - foreach (split(/\,/,$therelated[$ridx])) { - my ($rrridx,$rrrcount)=split(/\:/,$_); - unless ($found{$rrridx}) { - my $revrevrel=$rrrcount/$thecount[$ridx]; - if ( - $directrel*$revrel*$revrevrel>$thethreshold - ) { - $found{$rrridx}= - $directrel*$revrel*$revrevrel; - } + + +sub authorbrowser_javascript { + return <<"ENDAUTHORBRW"; + +ENDAUTHORBRW +} + +sub coursebrowser_javascript { + my ($domainfilter,$sec_element,$formname,$role_element,$crstype, + $credits_element,$instcode) = @_; + my $wintitle = 'Course_Browser'; + if ($crstype eq 'Community') { + $wintitle = 'Community_Browser'; + } + 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 javascript_array_indexof { + return < +// >> 0; + if (len === 0) { + return -1; + } + var n = 0; + if (arguments.length > 0) { + n = Number(arguments[1]); + if (n !== n) { // shortcut for verifying if it is NaN + n = 0; + } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); + for (; k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; + } +} + +// ]]> + + +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++) { + if (!field[i].disabled) { + field[i].checked = true; + } + } + } else { + if (!field.disabled) { + 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,$disabled)=@_; + 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 unless a defined order is provided. + +linked_select_forms takes the following ordered inputs: + +=over 4 + +=item * $formname, the name of the
tag + +=item * $middletext, the text which appears between the tag + +=item * $secondselectname, the name of the second tag + +=item * $onchangesecond, additional javascript call to execute for an onchange + event for the second \n"; + my @order = sort(keys(%{$hashref})); + if (ref($menuorder) eq 'ARRAY') { + @order = @{$menuorder}; + } + foreach my $value (@order) { + $result.=" \n"; + } + $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,$imgid) + +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. + +$imgid is the id of the img tag used for the help icon. This may be +used in a javascript call to switch the image src. See +lonhtmlcommon::htmlareaselectactive() for an example. + +=cut + +sub help_open_topic { + my ($topic, $text, $stayOnPage, $width, $height, $imgid) = @_; + $text = "" if (not defined $text); + $stayOnPage = 0 if (not defined $stayOnPage); + $width = 500 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:openMyModal('/adm/help/${filename}.hlp',$width,$height,'yes');"; + } elsif ($stayOnPage eq 'popup') { + $link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))"; + } else { + $link = "/adm/help/${filename}.hlp"; + } + + # Add the text + if ($text ne "") { + $template.='' + .'' + .$text.''; + } + + # (Always) Add the graphic + my $title = &mt('Online Help'); + my $helpicon=&lonhttpdurl("/adm/help/help.png"); + if ($imgid ne '') { + $imgid = ' id="'.$imgid.'"'; + } + $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,$stayOnPage) = @_; + my $out; + my $addOther = ''; + if ($topic) { + $addOther = ''.&help_open_topic($topic,&mt($text),$stayOnPage, undef, 600).' '; + } + $out = '' # Start cheatsheet + .$addOther + .'' + .&help_open_topic('Greek_Symbols',&mt('Greek Symbols'),$stayOnPage,undef,600) + .' ' + .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600) + .''; + unless ($not_author) { + $out .= '' + .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600) + .' ' + .&help_open_topic('Authoring_Multilingual_Problems',&mt('How to create problems in different languages'),$stayOnPage,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 = 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 = 1; + + my ($link,$banner_link); + unless ($env{'request.noversionuri'} =~ m{^/adm/helpmenu}) { + $link = ($stay_on_page) ? "javascript:helpMenu('display')" + : "javascript:helpMenu('open')"; + $banner_link = &update_help_link(undef,undef,undef,undef,$stay_on_page); + } + my $title = &mt('Get help'); + if ($link) { + return <<"END"; +$banner_link +$text +END + } else { + return ' '.$text.' '; + } +} + +sub help_menu_js { + my ($httphost) = @_; + my $stayOnPage = 1; + my $width = 620; + my $height = 600; + my $helptopic=&general_help(); + my $details_link = $httphost.'/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, + 'use_absolute' => $httphost, + '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=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=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 + +} + +sub colorfuleditor_js { + my $browse_or_search; + my $respath; + my ($cnum,$cdom) = &crsauthor_url(); + if ($cnum) { + $respath = "/res/$cdom/$cnum/"; + my %js_lt = &Apache::lonlocal::texthash( + sunm => 'Sub-directory name', + save => 'Save page to make this permanent', + ); + &js_escape(\%js_lt); + $browse_or_search = <<"END"; + + function toggleChooser(form,element,titleid,only,search) { + var disp = 'none'; + if (document.getElementById('chooser_'+element)) { + var curr = document.getElementById('chooser_'+element).style.display; + if (curr == 'none') { + disp='inline'; + if (form.elements['chooser_'+element].length) { + for (var i=0; i 1) { + window['select1'+element+'_changed'](); + } + } + } + document.getElementById('chooser_'+element+'_crsres').style.display = 'block'; + + } + if (document.getElementById('chooser_'+element+'_upload')) { + document.getElementById('chooser_'+element+'_upload').style.display = 'none'; + if (document.getElementById('uploadcrsres_'+element)) { + document.getElementById('uploadcrsres_'+element).value = ''; + } + } + return; + } + + function toggleCrsUpload(form,element,numcrsdirs) { + if (document.getElementById('chooser_'+element+'_crsres')) { + document.getElementById('chooser_'+element+'_crsres').style.display = 'none'; + } + if (document.getElementById('chooser_'+element+'_upload')) { + var curr = document.getElementById('chooser_'+element+'_upload').style.display; + if (curr == 'none') { + if (numcrsdirs) { + form.elements['crsauthorpath_'+element].selectedIndex = 0; + form.elements['newsubdir_'+element][0].checked = true; + toggleNewsubdir(form,element); + } + } + document.getElementById('chooser_'+element+'_upload').style.display = 'block'; + } + return; + } + + function toggleResImport(form,element) { + var choices = new Array('crsres','upload'); + for (var i=0; i +// + function fold_box(curDepth, lastresource){ + + // we need a list because there can be several blocks you need to fold in one tag + var block = document.getElementsByName('foldblock_'+curDepth); + // but there is only one folding button per tag + var foldbutton = document.getElementById('folding_btn_'+curDepth); + + if(block.item(0).style.display == 'none'){ + + foldbutton.value = '@{[&mt("Hide")]}'; + for (i = 0; i < block.length; i++){ + block.item(i).style.display = ''; + } + }else{ + + foldbutton.value = '@{[&mt("Show")]}'; + for (i = 0; i < block.length; i++){ + // block.item(i).style.visibility = 'collapse'; + block.item(i).style.display = 'none'; + } + }; + saveState(lastresource); + } + + function saveState (lastresource) { + + var tag_list = getTagList(); + if(tag_list != null){ + var timestamp = new Date().getTime(); + var key = lastresource; + + // the value pattern is: 'time;key1,value1;key2,value2; ... ' + // starting with timestamp + var value = timestamp+';'; + + // building the list of key-value pairs + for(var i = 0; i < tag_list.length; i++){ + value += tag_list[i]+','; + value += document.getElementsByName(tag_list[i])[0].style.display+';'; + } + + // only iterate whole storage if nothing to override + if(localStorage.getItem(key) == null){ + + // prevent storage from growing large + if(localStorage.length > 50){ + var regex_getTimestamp = /^(?:\d)+;/; + var oldest_timestamp = regex_getTimestamp.exec(localStorage.key(0)); + var oldest_key; + + for(var i = 1; i < localStorage.length; i++){ + if (regex_getTimestamp.exec(localStorage.key(i)) < oldest_timestamp) { + oldest_key = localStorage.key(i); + oldest_timestamp = regex_getTimestamp.exec(oldest_key); + } + } + localStorage.removeItem(oldest_key); + } + } + localStorage.setItem(key,value); + } + } + + // restore folding status of blocks (on page load) + function restoreState (lastresource) { + if(localStorage.getItem(lastresource) != null){ + var key = lastresource; + var value = localStorage.getItem(key); + var regex_delTimestamp = /^\d+;/; + + value.replace(regex_delTimestamp, ''); + + var valueArr = value.split(';'); + var pairs; + var elements; + for (var i = 0; i < valueArr.length; i++){ + pairs = valueArr[i].split(','); + elements = document.getElementsByName(pairs[0]); + + for (var j = 0; j < elements.length; j++){ + elements[j].style.display = pairs[1]; + if (pairs[1] == "none"){ + var regex_id = /([_\\d]+)\$/; + regex_id.exec(pairs[0]); + document.getElementById("folding_btn"+RegExp.\$1).value = "Show"; + } + } + } + } + } + + function getTagList () { + + var stringToSearch = document.lonhomework.innerHTML; + + var ret = new Array(); + var regex_findBlock = /(foldblock_.*?)"/g; + var tag_list = stringToSearch.match(regex_findBlock); + + if(tag_list != null){ + for(var i = 0; i < tag_list.length; i++){ + ret.push(tag_list[i].replace(/"/, '')); + } + } + return ret; + } + + function saveScrollPosition (resource) { + var tag_list = getTagList(); + + // we dont always want to jump to the first block + // 170 is roughly above the "Problem Editing" header. we just want to save if the user scrolled down further than this + if(\$(window).scrollTop() > 170){ + if(tag_list != null){ + var result; + for(var i = 0; i < tag_list.length; i++){ + if(isElementInViewport(tag_list[i])){ + result += tag_list[i]+';'; + } + } + sessionStorage.setItem('anchor_'+resource, result); + } + } else { + // we dont need to save zero, just delete the item to leave everything tidy + sessionStorage.removeItem('anchor_'+resource); + } + } + + function restoreScrollPosition(resource){ + + var elem = sessionStorage.getItem('anchor_'+resource); + if(elem != null){ + var tag_list = elem.split(';'); + var elem_list; + + for(var i = 0; i < tag_list.length; i++){ + elem_list = document.getElementsByName(tag_list[i]); + + if(elem_list.length > 0){ + elem = elem_list[0]; + break; + } + } + elem.scrollIntoView(); + } + } + + function isElementInViewport(el) { + + // change to last element instead of first + var elem = document.getElementsByName(el); + var rect = elem[0].getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ + rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ + ); + } + + function autosize(depth){ + var cmInst = window['cm'+depth]; + var fitsizeButton = document.getElementById('fitsize'+depth); + + // is fixed size, switching to dynamic + if (sessionStorage.getItem("autosized_"+depth) == null) { + cmInst.setSize("","auto"); + fitsizeButton.value = "@{[&mt('Fixed size')]}"; + sessionStorage.setItem("autosized_"+depth, "yes"); + + // is dynamic size, switching to fixed + } else { + cmInst.setSize("","300px"); + fitsizeButton.value = "@{[&mt('Dynamic size')]}"; + sessionStorage.removeItem("autosized_"+depth); + } + } + +$browse_or_search + +// ]]> + +COLORFULEDIT +} + +sub xmleditor_js { + return < + +XMLEDIT +} + +sub insert_folding_button { + my $curDepth = $Apache::lonxml::curdepth; + my $lastresource = $env{'request.ambiguous'}; + + return ""; +} + +sub crsauthor_url { + my ($url) = @_; + if ($url eq '') { + $url = $ENV{'REQUEST_URI'}; + } + my ($cnum,$cdom); + if ($env{'request.course.id'}) { + my ($audom,$auname) = ($url =~ m{^/priv/($match_domain)/($match_name)/}); + if ($audom ne '' && $auname ne '') { + if (($env{'course.'.$env{'request.course.id'}.'.num'} eq $auname) && + ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $audom)) { + $cnum = $auname; + $cdom = $audom; + } + } + } + return ($cnum,$cdom); +} + +sub import_crsauthor_form { + my ($form,$firstselectname,$secondselectname,$onchangefirst,$only,$suffix,$disabled) = @_; + return (0) unless ($env{'request.course.id'}); + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $crshome = $env{'course.'.$env{'request.course.id'}.'.home'}; + return (0) unless (($cnum ne '') && ($cdom ne '')); + my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; + my @ids=&Apache::lonnet::current_machine_ids(); + my ($output,$is_home,$relpath,%subdirs,%files,%selimport_menus); + + if (grep(/^\Q$crshome\E$/,@ids)) { + $is_home = 1; + } + $relpath = "/priv/$cdom/$cnum"; + &Apache::lonnet::recursedirs($is_home,'priv',$londocroot,$relpath,'',\%subdirs,\%files); + my %lt = &Apache::lonlocal::texthash ( + fnam => 'Filename', + dire => 'Directory', + ); + my $numdirs = scalar(keys(%files)); + my (%possexts,$singledir,@singledirfiles); + if ($only) { + map { $possexts{$_} = 1; } split(/\s*,\s*/,$only); + } + my (%nonemptydirs,$possdirs); + if ($numdirs > 1) { + my @order; + foreach my $key (sort { lc($a) cmp lc($b) } (keys(%files))) { + if (ref($files{$key}) eq 'HASH') { + my $shown = $key; + if ($key eq '') { + $shown = '/'; + } + my @ordered = (); + foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$key}}))) { + if ($only) { + my ($ext) = ($file =~ /\.([^.]+)$/); + unless ($possexts{lc($ext)}) { + next; + } + } + $selimport_menus{$key}->{'select2'}->{$file} = $file; + push(@ordered,$file); + } + if (@ordered) { + push(@order,$key); + $nonemptydirs{$key} = 1; + $selimport_menus{$key}->{'text'} = $shown; + $selimport_menus{$key}->{'default'} = ''; + $selimport_menus{$key}->{'select2'}->{''} = ''; + $selimport_menus{$key}->{'order'} = \@ordered; + } + } + } + $possdirs = scalar(keys(%nonemptydirs)); + if ($possdirs > 1) { + my @order = sort { lc($a) cmp lc($b) } (keys(%nonemptydirs)); + $output = $lt{'dire'}. + &linked_select_forms($form,'
'. + $lt{'fnam'},'', + $firstselectname,$secondselectname, + \%selimport_menus,\@order, + $onchangefirst,'',$suffix).'
'; + } elsif ($possdirs == 1) { + $singledir = (keys(%nonemptydirs))[0]; + if (ref($selimport_menus{$singledir}->{'order'}) eq 'ARRAY') { + @singledirfiles = @{$selimport_menus{$singledir}->{'order'}}; + } + delete($selimport_menus{$singledir}); + } + } elsif ($numdirs == 1) { + $singledir = (keys(%files))[0]; + foreach my $file (sort { lc($a) cmp lc($b) } (keys(%{$files{$singledir}}))) { + if ($only) { + my ($ext) = ($file =~ /\.([^.]+)$/); + unless ($possexts{lc($ext)}) { + next; + } + } + push(@singledirfiles,$file); + } + if (@singledirfiles) { + $possdirs == 1; + } + } + if (($possdirs == 1) && (@singledirfiles)) { + my $showdir = $singledir; + if ($singledir eq '') { + $showdir = '/'; + } + $output = $lt{'dire'}. + '
'. + $lt{'fnam'}.'
'."\n"; + } + return ($possdirs,$output); +} + +=pod + +=head1 Excel and CSV file utility routines + +=cut + +############################################################### +############################################################### + +=pod + +=over 4 + +=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; } -# ---------------------------------------------------------------- Language IDs +############################################################### +############################################################### + +=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('Problems occurred in creating the new Excel file.') + .' '.&mt('This error has been logged.') + .' '.&mt('Please alert your LON-CAPA administrator.') + .'

' + ); + return (undef); + } + # + $workbook->set_tempdir(LONCAPA::tempdir()); + # + 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.') + .' '.&mt('This error has been logged.') + .' '.&mt('Please alert your LON-CAPA administrator.') + .'

' + ); + } + return ($fh,$filename) +} + + +=pod + +=back + +=cut + +############################################################### +## Home server \n"; + } + $selectform.=""; + return $selectform; +} + +# For display filters + +sub display_filter { + my ($context) = @_; + if (!$env{'form.show'}) { $env{'form.show'}=10; } + if (!$env{'form.displayfilter'}) { $env{'form.displayfilter'}='currentfolder'; } + my $phraseinput = 'hidden'; + my $includeinput = 'hidden'; + my ($checked,$includetypestext); + if ($env{'form.displayfilter'} eq 'containing') { + $phraseinput = 'text'; + if ($context eq 'parmslog') { + $includeinput = 'checkbox'; + if ($env{'form.includetypes'}) { + $checked = ' checked="checked"'; + } + $includetypestext = &mt('Include parameter types'); + } + } else { + $includetypestext = ' '; + } + my ($additional,$secondid,$thirdid); + if ($context eq 'parmslog') { + $additional = + ''; + $secondid = 'includetypes'; + $thirdid = 'includetypestext'; + } + my $onchange = "javascript:toggleHistoryOptions(this,'containingphrase','$context', + '$secondid','$thirdid')"; + return ' '. + &mt('Filter: [_1]', + &select_form($env{'form.displayfilter'}, + 'displayfilter', + {'currentfolder' => 'Current folder/page', + 'containing' => 'Containing phrase', + 'none' => 'None'},$onchange)).' '. + ''.$additional; +} + +sub display_filter_js { + my $includetext = &mt('Include parameter types'); + return <<"ENDJS"; + +function toggleHistoryOptions(setter,firstid,context,secondid,thirdid) { + var firstType = 'hidden'; + if (setter.options[setter.selectedIndex].value == 'containing') { + firstType = 'text'; + } + firstObject = document.getElementById(firstid); + if (typeof(firstObject) == 'object') { + if (firstObject.type != firstType) { + changeInputType(firstObject,firstType); + } + } + if (context == 'parmslog') { + var secondType = 'hidden'; + if (firstType == 'text') { + secondType = 'checkbox'; + } + secondObject = document.getElementById(secondid); + if (typeof(secondObject) == 'object') { + if (secondObject.type != secondType) { + changeInputType(secondObject,secondType); + } + } + var textItem = document.getElementById(thirdid); + var currtext = textItem.innerHTML; + var newtext; + if (firstType == 'text') { + newtext = '$includetext'; + } else { + newtext = ' '; + } + if (currtext != newtext) { + textItem.innerHTML = newtext; + } + } + return; +} + +function changeInputType(oldObject,newType) { + var newObject = document.createElement('input'); + newObject.type = newType; + if (oldObject.size) { + newObject.size = oldObject.size; + } + if (oldObject.value) { + newObject.value = oldObject.value; + } + if (oldObject.name) { + newObject.name = oldObject.name; + } + if (oldObject.id) { + newObject.id = oldObject.id; + } + oldObject.parentNode.replaceChild(newObject,oldObject); + return; +} + +ENDJS +} + +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,$showdomdesc,$onchange,$incdoms,$excdoms,$disabled) + +Returns a string containing a \n"; + foreach my $dom (@domains) { + next if ($exclude{$dom}); + $selectdomain.="\n"; + } + $selectdomain.=""; + return $selectdomain; +} + +#------------------------------------------- + +=pod + +=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 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 ($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 * $clientmathml + +=item * $clientunicode + +=item * $clientos + +=item * $clientmobile + +=item * $clientinfo + +=item * $clientosversion + +=back + +=back + +=cut + +############################################################### +############################################################### +sub decode_user_agent { + my ($r)=@_; + my @browsertype=split(/\&/,$Apache::lonnet::perlvar{"lonBrowsDet"}); + my %mathcap=split(/\&/,$$Apache::lonnet::perlvar{"lonMathML"}); + my $httpbrowser=$ENV{"HTTP_USER_AGENT"}; + if (!$httpbrowser && $r) { $httpbrowser=$r->header_in('User-Agent'); } + my $clientbrowser='unknown'; + my $clientversion='0'; + my $clientmathml=''; + my $clientunicode='0'; + my $clientmobile=0; + my $clientosversion=''; + for (my $i=0;$i<=$#browsertype;$i++) { + my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\%/,$browsertype[$i]); + if (($httpbrowser=~/$match/i) && ($httpbrowser!~/$notmatch/i)) { + $clientbrowser=$bname; + $httpbrowser=~/$vreg/i; + $clientversion=$1; + $clientmathml=($clientversion>=$minv); + $clientunicode=($clientversion>=$univ); + } + } + my $clientos='unknown'; + my $clientinfo; + if (($httpbrowser=~/linux/i) || + ($httpbrowser=~/unix/i) || + ($httpbrowser=~/ux/i) || + ($httpbrowser=~/solaris/i)) { $clientos='unix'; } + if (($httpbrowser=~/vax/i) || + ($httpbrowser=~/vms/i)) { $clientos='vms'; } + if ($httpbrowser=~/next/i) { $clientos='next'; } + if (($httpbrowser=~/mac/i) || + ($httpbrowser=~/powerpc/i)) { $clientos='mac'; } + if ($httpbrowser=~/win/i) { + $clientos='win'; + if ($httpbrowser =~/Windows\s+NT\s+(\d+\.\d+)/i) { + $clientosversion = $1; + } + } + if ($httpbrowser=~/embed/i) { $clientos='pda'; } + if ($httpbrowser=~/(Android|iPod|iPad|iPhone|webOS|Blackberry|Windows Phone|Opera m(?:ob|in)|Fennec)/i) { + $clientmobile=lc($1); + } + if ($httpbrowser=~ m{Firefox/(\d+\.\d+)}) { + $clientinfo = 'firefox-'.$1; + } elsif ($httpbrowser=~ m{chromeframe/(\d+\.\d+)\.}) { + $clientinfo = 'chromeframe-'.$1; + } + return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml, + $clientunicode,$clientos,$clientmobile,$clientinfo, + $clientosversion); +} + +############################################################### +## Authentication changing form generation subroutines ## +############################################################### +## +## All of the authform_xxxxxxx subroutines take their inputs in a +## hash, and have reasonable default values. +## +## formname = the name given in the tag. +#------------------------------------------- + +=pod + +=head1 Authentication Routines + +=over 4 + +=item * &authform_xxxxxx() + +The authform_xxxxxx subroutines provide javascript and html forms which +handle some of the conveniences required for authentication forms. +This is not an optimal method, but it works. + +=over 4 + +=item * authform_header + +=item * authform_authorwarning + +=item * authform_nochange + +=item * authform_kerberos + +=item * authform_internal + +=item * authform_filesystem + +=back + +See loncreateuser.pm for invocation and use examples. + +=cut + +#------------------------------------------- +sub authform_header{ + my %in = ( + formname => 'cu', + kerb_def_dom => '', + @_, + ); + $in{'formname'} = 'document.' . $in{'formname'}; + my $result=''; + +#---------------------------------------------- Code for upper case translation + my $Javascript_toUpperCase; + unless ($in{kerb_def_dom}) { + $Javascript_toUpperCase =<<"END"; + switch (choice) { + case 'krb': currentform.elements[choicearg].value = + currentform.elements[choicearg].value.toUpperCase(); + break; + default: + } +END + } else { + $Javascript_toUpperCase = ""; + } + + my $radioval = "'nochange'"; + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} ne '') { + $radioval = "'".$in{'curr_authtype'}."arg'"; + } + } + my $argfield = 'null'; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if (defined($in{'curr_autharg'})) { + if ($in{'curr_autharg'} ne '') { + $argfield = "'$in{'curr_autharg'}'"; + } + } + } + } + + $result.=<<"END"; +var current = new Object(); +current.radiovalue = $radioval; +current.argfield = $argfield; + +function changed_radio(choice,currentform) { + var choicearg = choice + 'arg'; + // If a radio button in changed, we need to change the argfield + if (current.radiovalue != choice) { + current.radiovalue = choice; + if (current.argfield != null) { + currentform.elements[current.argfield].value = ''; + } + if (choice == 'nochange') { + current.argfield = null; + } else { + current.argfield = choicearg; + switch(choice) { + case 'krb': + currentform.elements[current.argfield].value = + "$in{'kerb_def_dom'}"; + break; + default: + break; + } + } + } + return; +} + +function changed_text(choice,currentform) { + var choicearg = choice + 'arg'; + if (currentform.elements[choicearg].value !='') { + $Javascript_toUpperCase + // clear old field + if ((current.argfield != choicearg) && (current.argfield != null)) { + currentform.elements[current.argfield].value = ''; + } + current.argfield = choicearg; + } + set_auth_radio_buttons(choice,currentform); + return; +} + +function set_auth_radio_buttons(newvalue,currentform) { + var numauthchoices = currentform.login.length; + if (typeof numauthchoices == "undefined") { + return; + } + var i=0; + while (i < numauthchoices) { + if (currentform.login[i].value == newvalue) { break; } + i++; + } + if (i == numauthchoices) { + return; + } + current.radiovalue = newvalue; + currentform.login[i].checked = true; + return; +} +END + return $result; +} + +sub authform_authorwarning { + my $result=''; + $result=''. + &mt('As a general rule, only authors or co-authors should be '. + 'filesystem authenticated '. + '(which allows access to the server filesystem).')."\n"; + return $result; +} + +sub authform_nochange { + my %in = ( + formname => 'document.cu', + kerb_def_dom => 'MSU.EDU', + @_, + ); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + my $result; + if (!$authnum) { + $result = &mt('Under your current role you are not permitted to change login settings for this user'); + } else { + $result = ''; + } + return $result; +} + +sub authform_kerberos { + my %in = ( + formname => 'document.cu', + kerb_def_dom => 'MSU.EDU', + kerb_def_auth => 'krb4', + @_, + ); + my ($check4,$check5,$krbcheck,$krbarg,$krbver,$result,$authtype, + $autharg,$jscall,$disabled); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + if ($in{'kerb_def_auth'} eq 'krb5') { + $check5 = ' checked="checked"'; + } else { + $check4 = ' checked="checked"'; + } + if ($in{'readonly'}) { + $disabled = ' disabled="disabled"'; + } + $krbarg = $in{'kerb_def_dom'}; + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} eq 'krb') { + $krbcheck = ' checked="checked"'; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifyuser') { + $krbcheck = ''; + } + } + if (defined($in{'curr_kerb_ver'})) { + if ($in{'curr_krb_ver'} eq '5') { + $check5 = ' checked="checked"'; + $check4 = ''; + } else { + $check4 = ' checked="checked"'; + $check5 = ''; + } + } + if (defined($in{'curr_autharg'})) { + $krbarg = $in{'curr_autharg'}; + } + if (!$can_assign{'krb4'} && !$can_assign{'krb5'}) { + if (defined($in{'curr_autharg'})) { + $result = + &mt('Currently Kerberos authenticated with domain [_1] Version [_2].', + $in{'curr_autharg'},$krbver); + } else { + $result = + &mt('Currently Kerberos authenticated, Version [_1].',$krbver); + } + return $result; + } + } + } else { + if ($authnum == 1) { + $authtype = ''; + } + } + if (!$can_assign{'krb4'} && !$can_assign{'krb5'}) { + return; + } elsif ($authtype eq '') { + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if ($authnum == 1) { + $authtype = ''; + } + } + } + } + $jscall = "javascript:changed_radio('krb',$in{'formname'});"; + if ($authtype eq '') { + $authtype = ''; + } + if (($can_assign{'krb4'} && $can_assign{'krb5'}) || + ($can_assign{'krb4'} && !$can_assign{'krb5'} && + $in{'curr_authtype'} eq 'krb5') || + (!$can_assign{'krb4'} && $can_assign{'krb5'} && + $in{'curr_authtype'} eq 'krb4')) { + $result .= &mt + ('[_1] Kerberos authenticated with domain [_2] '. + '[_3] Version 4 [_4] Version 5 [_5]', + '', + ''); + } elsif ($can_assign{'krb4'}) { + $result .= &mt + ('[_1] Kerberos authenticated with domain [_2] '. + '[_3] Version 4 [_4]', + '', + ''); + } elsif ($can_assign{'krb5'}) { + $result .= &mt + ('[_1] Kerberos authenticated with domain [_2] '. + '[_3] Version 5 [_4]', + '', + ''); + } + return $result; +} + +sub authform_internal { + my %in = ( + formname => 'document.cu', + kerb_def_dom => 'MSU.EDU', + @_, + ); + my ($intcheck,$intarg,$result,$authtype,$autharg,$jscall,$disabled); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + if ($in{'readonly'}) { + $disabled = ' disabled="disabled"'; + } + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} eq 'int') { + if ($can_assign{'int'}) { + $intcheck = 'checked="checked" '; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifyuser') { + $intcheck = ''; + } + } + if (defined($in{'curr_autharg'})) { + $intarg = $in{'curr_autharg'}; + } + } else { + $result = &mt('Currently internally authenticated.'); + return $result; + } + } + } else { + if ($authnum == 1) { + $authtype = ''; + } + } + if (!$can_assign{'int'}) { + return; + } elsif ($authtype eq '') { + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if ($authnum == 1) { + $authtype = ''; + } + } + } + } + $jscall = "javascript:changed_radio('int',$in{'formname'});"; + if ($authtype eq '') { + $authtype = ''; + } + $autharg = ''; + $result = &mt + ('[_1] Internally authenticated (with initial password [_2])', + ''.$autharg); + $result.=''; + return $result; +} + +sub authform_local { + my %in = ( + formname => 'document.cu', + kerb_def_dom => 'MSU.EDU', + @_, + ); + my ($loccheck,$locarg,$result,$authtype,$autharg,$jscall,$disabled); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + if ($in{'readonly'}) { + $disabled = ' disabled="disabled"'; + } + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} eq 'loc') { + if ($can_assign{'loc'}) { + $loccheck = 'checked="checked" '; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifyuser') { + $loccheck = ''; + } + } + if (defined($in{'curr_autharg'})) { + $locarg = $in{'curr_autharg'}; + } + } else { + $result = &mt('Currently using local (institutional) authentication.'); + return $result; + } + } + } else { + if ($authnum == 1) { + $authtype = ''; + } + } + if (!$can_assign{'loc'}) { + return; + } elsif ($authtype eq '') { + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if ($authnum == 1) { + $authtype = ''; + } + } + } + } + $jscall = "javascript:changed_radio('loc',$in{'formname'});"; + if ($authtype eq '') { + $authtype = ''; + } + $autharg = ''; + $result = &mt('[_1] Local Authentication with argument [_2]', + ''.$autharg); + return $result; +} + +sub authform_filesystem { + my %in = ( + formname => 'document.cu', + kerb_def_dom => 'MSU.EDU', + @_, + ); + my ($fsyscheck,$result,$authtype,$autharg,$jscall,$disabled); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + if ($in{'readonly'}) { + $disabled = ' disabled="disabled"'; + } + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} eq 'fsys') { + if ($can_assign{'fsys'}) { + $fsyscheck = 'checked="checked" '; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifyuser') { + $fsyscheck = ''; + } + } + } else { + $result = &mt('Currently Filesystem Authenticated.'); + return $result; + } + } + } else { + if ($authnum == 1) { + $authtype = ''; + } + } + if (!$can_assign{'fsys'}) { + return; + } elsif ($authtype eq '') { + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if ($authnum == 1) { + $authtype = ''; + } + } + } + } + $jscall = "javascript:changed_radio('fsys',$in{'formname'});"; + if ($authtype eq '') { + $authtype = ''; + } + $autharg = ''; + $result = &mt + ('[_1] Filesystem Authenticated (with initial password [_2])', + ''); + return $result; +} + +sub get_assignable_auth { + my ($dom) = @_; + if ($dom eq '') { + $dom = $env{'request.role.domain'}; + } + my %can_assign = ( + krb4 => 1, + krb5 => 1, + int => 1, + loc => 1, + ); + my %domconfig = &Apache::lonnet::get_dom('configuration',['usercreation'],$dom); + if (ref($domconfig{'usercreation'}) eq 'HASH') { + if (ref($domconfig{'usercreation'}{'authtypes'}) eq 'HASH') { + my $authhash = $domconfig{'usercreation'}{'authtypes'}; + my $context; + if ($env{'request.role'} =~ /^au/) { + $context = 'author'; + } elsif ($env{'request.role'} =~ /^(dc|dh)/) { + $context = 'domain'; + } elsif ($env{'request.course.id'}) { + $context = 'course'; + } + if ($context) { + if (ref($authhash->{$context}) eq 'HASH') { + %can_assign = %{$authhash->{$context}}; + } + } + } + } + my $authnum = 0; + foreach my $key (keys(%can_assign)) { + if ($can_assign{$key}) { + $authnum ++; + } + } + if ($can_assign{'krb4'} && $can_assign{'krb5'}) { + $authnum --; + } + return ($authnum,%can_assign); +} + +############################################################### +## Get Kerberos Defaults for Domain ## +############################################################### +## +## Returns default kerberos version and an associated argument +## as listed in file domain.tab. If not listed, provides +## appropriate default domain and kerberos version. +## +#------------------------------------------- + +=pod + +=item * &get_kerberos_defaults() + +get_kerberos_defaults($target_domain) returns the default kerberos +version and domain. If not found, it defaults to version 4 and the +domain of the server. + +=over 4 + +($def_version, $def_krb_domain) = &get_kerberos_defaults($target_domain); + +=back + +=back + +=cut + +#------------------------------------------- +sub get_kerberos_defaults { + my $domain=shift; + my ($krbdef,$krbdefdom); + my %domdefaults = &Apache::lonnet::get_domain_defaults($domain); + if (($domdefaults{'auth_def'} =~/^krb(4|5)$/) && ($domdefaults{'auth_arg_def'} ne '')) { + $krbdef = $domdefaults{'auth_def'}; + $krbdefdom = $domdefaults{'auth_arg_def'}; + } else { + $ENV{'SERVER_NAME'}=~/(\w+\.\w+)$/; + my $krbdefdom=$1; + $krbdefdom=~tr/a-z/A-Z/; + $krbdef = "krb4"; + } + return ($krbdef,$krbdefdom); +} + + +############################################################### +## Thesaurus Functions ## +############################################################### + +=pod + +=head1 Thesaurus Functions + +=over 4 + +=item * &initialize_keywords() + +Initializes the package variable %Keywords if it is empty. Uses the +package variable $thesaurus_db_file. + +=cut + +################################################### + +sub initialize_keywords { + return 1 if (scalar keys(%Keywords)); + # If we are here, %Keywords is empty, so fill it up + # Make sure the file we need exists... + if (! -e $thesaurus_db_file) { + &Apache::lonnet::logthis("Attempt to access $thesaurus_db_file". + " failed because it does not exist"); + return 0; + } + # Set up the hash as a database + my %thesaurus_db; + if (! tie(%thesaurus_db,'GDBM_File', + $thesaurus_db_file,&GDBM_READER(),0640)){ + &Apache::lonnet::logthis("Could not tie \%thesaurus_db to ". + $thesaurus_db_file); + return 0; + } + # Get the average number of appearances of a word. + my $avecount = $thesaurus_db{'average.count'}; + # Put keywords (those that appear > average) into %Keywords + while (my ($word,$data)=each (%thesaurus_db)) { + my ($count,undef) = split /:/,$data; + $Keywords{$word}++ if ($count > $avecount); + } + untie %thesaurus_db; + # Remove special values from %Keywords. + foreach my $value ('total.count','average.count') { + delete($Keywords{$value}) if (exists($Keywords{$value})); + } + return 1; +} + +################################################### + +=pod + +=item * &keyword($word) + +Returns true if $word is a keyword. A keyword is a word that appears more +than the average number of times in the thesaurus database. Calls +&initialize_keywords + +=cut + +################################################### + +sub keyword { + return if (!&initialize_keywords()); + my $word=lc(shift()); + $word=~s/\W//g; + return exists($Keywords{$word}); +} + +############################################################### + +=pod + +=item * &get_related_words() + +Look up a word in the thesaurus. Takes a scalar argument and returns +an array of words. If the keyword is not in the thesaurus, an empty array +will be returned. The order of the words returned is determined by the +database which holds them. + +Uses global $thesaurus_db_file. + + +=cut + +############################################################### +sub get_related_words { + my $keyword = shift; + my %thesaurus_db; + if (! -e $thesaurus_db_file) { + &Apache::lonnet::logthis("Attempt to access $thesaurus_db_file ". + "failed because the file does not exist"); + return (); + } + if (! tie(%thesaurus_db,'GDBM_File', + $thesaurus_db_file,&GDBM_READER(),0640)){ + return (); + } + my @Words=(); + my $count=0; + if (exists($thesaurus_db{$keyword})) { + # The first element is the number of times + # the word appears. We do not need it now. + my (undef,@RelatedWords) = (split(/:/,$thesaurus_db{$keyword})); + my (undef,$mostfrequentcount)=split(/\,/,$RelatedWords[0]); + my $threshold=$mostfrequentcount/10; + foreach my $possibleword (@RelatedWords) { + my ($word,$wordcount)=split(/\,/,$possibleword); + if ($wordcount>$threshold) { + push(@Words,$word); + $count++; + if ($count>10) { last; } + } + } + } + untie %thesaurus_db; + return @Words; +} +############################################################### +# +# Spell checking +# + +=pod + +=back + +=head1 Spell checking + +=over 4 + +=item * &check_spelling($wordlist $language) + +Takes a string containing words and feeds it to an external +spellcheck program via a pipeline. Returns a string containing +them mis-spelled words. + +Parameters: + +=over 4 + +=item - $wordlist + +String that will be fed into the spellcheck program. + +=item - $language + +Language string that specifies the language for which the spell +check will be performed. + +=back + +=back + +Note: This sub assumes that aspell is installed. + + +=cut + + +sub check_spelling { + my ($wordlist, $language) = @_; + my @misspellings; + + # Generate the speller and set the langauge. + # if explicitly selected: + + my $speller = Text::Aspell->new; + if ($language) { + $speller->set_option('lang', $language); + } + + # Turn the word list into an array of words by splittingon whitespace + + my @words = split(/\s+/, $wordlist); + + foreach my $word (@words) { + if(! $speller->check($word)) { + push(@misspellings, $word); + } + } + return join(' ', @misspellings); + +} + +# -------------------------------------------------------------- Plaintext name +=pod + +=head1 User Name Functions + +=over 4 + +=item * &plainname($uname,$udom,$first) + +Takes a users logon name and returns it as a string in +"first middle last generation" form +if $first is set to 'lastname' then it returns it as +'lastname generation, firstname middlename' if their is a lastname + +=cut + + +############################################################### +sub plainname { + my ($uname,$udom,$first)=@_; + return if (!defined($uname) || !defined($udom)); + my %names=&getnames($uname,$udom); + my $name=&Apache::lonnet::format_name($names{'firstname'}, + $names{'middlename'}, + $names{'lastname'}, + $names{'generation'},$first); + $name=~s/^\s+//; + $name=~s/\s+$//; + $name=~s/\s+/ /g; + if ($name !~ /\S/) { $name=$uname.':'.$udom; } + return $name; +} + +# -------------------------------------------------------------------- Nickname +=pod + +=item * &nickname($uname,$udom) + +Gets a users name and returns it as a string as + +""nickname"" + +if the user has a nickname or + +"first middle last generation" + +if the user does not + +=cut + +sub nickname { + my ($uname,$udom)=@_; + return if (!defined($uname) || !defined($udom)); + my %names=&getnames($uname,$udom); + my $name=$names{'nickname'}; + if ($name) { + $name='"'.$name.'"'; + } else { + $name=$names{'firstname'}.' '.$names{'middlename'}.' '. + $names{'lastname'}.' '.$names{'generation'}; + $name=~s/\s+$//; + $name=~s/\s+/ /g; + } + return $name; +} + +sub getnames { + my ($uname,$udom)=@_; + return if (!defined($uname) || !defined($udom)); + if ($udom eq 'public' && $uname eq 'public') { + return ('lastname' => &mt('Public')); + } + my $id=$uname.':'.$udom; + my ($names,$cached)=&Apache::lonnet::is_cached_new('namescache',$id); + if ($cached) { + return %{$names}; + } else { + my %loadnames=&Apache::lonnet::get('environment', + ['firstname','middlename','lastname','generation','nickname'], + $udom,$uname); + &Apache::lonnet::do_cache_new('namescache',$id,\%loadnames); + return %loadnames; + } +} + +# -------------------------------------------------------------------- getemails + +=pod + +=item * &getemails($uname,$udom) + +Gets a user's email information and returns it as a hash with keys: +notification, critnotification, permanentemail + +For notification and critnotification, values are comma-separated lists +of e-mail addresses; for permanentemail, value is a single e-mail address. + + +=cut + + +sub getemails { + my ($uname,$udom)=@_; + if ($udom eq 'public' && $uname eq 'public') { + return; + } + if (!$udom) { $udom=$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + my $id=$uname.':'.$udom; + my ($names,$cached)=&Apache::lonnet::is_cached_new('emailscache',$id); + if ($cached) { + return %{$names}; + } else { + my %loadnames=&Apache::lonnet::get('environment', + ['notification','critnotification', + 'permanentemail'], + $udom,$uname); + &Apache::lonnet::do_cache_new('emailscache',$id,\%loadnames); + return %loadnames; + } +} + +sub flush_email_cache { + my ($uname,$udom)=@_; + if (!$udom) { $udom =$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $id=$uname.':'.$udom; + &Apache::lonnet::devalidate_cache_new('emailscache',$id); +} + +# -------------------------------------------------------------------- getlangs + +=pod + +=item * &getlangs($uname,$udom) + +Gets a user's language preference and returns it as a hash with key: +language. + +=cut + + +sub getlangs { + my ($uname,$udom) = @_; + if (!$udom) { $udom =$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + my $id=$uname.':'.$udom; + my ($langs,$cached)=&Apache::lonnet::is_cached_new('userlangs',$id); + if ($cached) { + return %{$langs}; + } else { + my %loadlangs=&Apache::lonnet::get('environment',['languages'], + $udom,$uname); + &Apache::lonnet::do_cache_new('userlangs',$id,\%loadlangs); + return %loadlangs; + } +} + +sub flush_langs_cache { + my ($uname,$udom)=@_; + if (!$udom) { $udom =$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $id=$uname.':'.$udom; + &Apache::lonnet::devalidate_cache_new('userlangs',$id); +} + +# ------------------------------------------------------------------ Screenname + +=pod + +=item * &screenname($uname,$udom) + +Gets a users screenname and returns it as a string + +=cut + +sub screenname { + my ($uname,$udom)=@_; + if ($uname eq $env{'user.name'} && + $udom eq $env{'user.domain'}) {return $env{'environment.screenname'};} + my %names=&Apache::lonnet::get('environment',['screenname'],$udom,$uname); + return $names{'screenname'}; +} + + +# ------------------------------------------------------------- Confirm Wrapper +=pod + +=item * &confirmwrapper($message) + +Wrap messages about completion of operation in box + +=cut + +sub confirmwrapper { + my ($message)=@_; + if ($message) { + return "\n".'
'."\n" + .$message."\n" + .'
'."\n"; + } else { + return $message; + } +} + +# ------------------------------------------------------------- Message Wrapper + +sub messagewrapper { + my ($link,$username,$domain,$subject,$text)=@_; + return + ''.$link.''; +} + +# --------------------------------------------------------------- Notes Wrapper + +sub noteswrapper { + my ($link,$un,$do)=@_; + return +"$link"; +} + +# ------------------------------------------------------------- Aboutme Wrapper + +sub aboutmewrapper { + my ($link,$username,$domain,$target,$class)=@_; + if (!defined($username) && !defined($domain)) { + return; + } + return ''.$link.''; +} + +# ------------------------------------------------------------ Syllabus Wrapper + +sub syllabuswrapper { + my ($linktext,$coursedir,$domain)=@_; + return qq{$linktext}; +} + +# ----------------------------------------------------------------------------- + +sub track_student_link { + my ($linktext,$sname,$sdom,$target,$start,$only_body) = @_; + my $link ="/adm/trackstudent?"; + my $title = 'View recent activity'; + if (defined($sname) && $sname !~ /^\s*$/ && + defined($sdom) && $sdom !~ /^\s*$/) { + $link .= "selected_student=$sname:$sdom"; + $title .= ' of this student'; + } + if (defined($target) && $target !~ /^\s*$/) { + $target = qq{target="$target"}; + } else { + $target = ''; + } + if ($start) { $link.='&start='.$start; } + if ($only_body) { $link .= '&only_body=1'; } + $title = &mt($title); + $linktext = &mt($linktext); + return qq{$linktext}. + &help_open_topic('View_recent_activity'); +} + +sub slot_reservations_link { + my ($linktext,$sname,$sdom,$target) = @_; + my $link ="/adm/slotrequest?command=showresv&origin=aboutme"; + my $title = 'View slot reservation history'; + if (defined($sname) && $sname !~ /^\s*$/ && + defined($sdom) && $sdom !~ /^\s*$/) { + $link .= "&uname=$sname&udom=$sdom"; + $title .= ' of this student'; + } + if (defined($target) && $target !~ /^\s*$/) { + $target = qq{target="$target"}; + } else { + $target = ''; + } + $title = &mt($title); + $linktext = &mt($linktext); + return qq{$linktext}; +# FIXME uncomment when help item created: &help_open_topic('Slot_Reservation_History'); + +} + +# ===================================================== Display a student photo + + +sub student_image_tag { + my ($domain,$user)=@_; + my $imgsrc=&Apache::lonnet::studentphoto($domain,$user,'jpg'); + if (($imgsrc) && ($imgsrc ne '/adm/lonKaputt/lonlogo_broken.gif')) { + return ''; + } else { + return ''; + } +} + +=pod + +=back + +=head1 Access .tab File Data + +=over 4 + +=item * &languageids() + +returns list of all language ids + +=cut + sub languageids { return sort(keys(%language)); } -# -------------------------------------------------------- Language Description +=pod + +=item * &languagedescription() + +returns description of a specified language id + +=cut + sub languagedescription { - return $language{shift(@_)}; + my $code=shift; + return ($supported_language{$code}?'* ':''). + $language{$code}. + ($supported_language{$code}?' ('.&mt('interface available').')':''); +} + +=pod + +=item * &plainlanguagedescription + +Returns both the plain language description (e.g. 'Creoles and Pidgins, English-based (Other)') +and the language character encoding (e.g. ISO) separated by a ' - ' string. + +=cut + +sub plainlanguagedescription { + my $code=shift; + return $language{$code}; +} + +=pod + +=item * &supportedlanguagecode + +Returns the supported language code (e.g. sptutf maps to pt) given a language +code. + +=cut + +sub supportedlanguagecode { + my $code=shift; + return $supported_language{$code}; +} + +=pod + +=item * &latexlanguage() + +Given a language key code returns the correspondnig language to use +to select the correct hyphenation on LaTeX printouts. This is undef if there +is no supported hyphenation for the language code. + +=cut + +sub latexlanguage { + my $code = shift; + return $latex_language{$code}; +} + +=pod + +=item * &latexhyphenation() + +Same as above but what's supplied is the language as it might be stored +in the metadata. + +=cut + +sub latexhyphenation { + my $key = shift; + return $latex_language_bykey{$key}; } -# --------------------------------------------------------------- Copyright IDs +=pod + +=item * ©rightids() + +returns list of all copyrights + +=cut + sub copyrightids { return sort(keys(%cprtag)); } -# ------------------------------------------------------- Copyright Description +=pod + +=item * ©rightdescription() + +returns description of a specified copyright id + +=cut + sub copyrightdescription { - return $cprtag{shift(@_)}; + return &mt($cprtag{shift(@_)}); } -# ------------------------------------------------------------- File Categories +=pod + +=item * &source_copyrightids() + +returns list of all source copyrights + +=cut + +sub source_copyrightids { + return sort(keys(%scprtag)); +} + +=pod + +=item * &source_copyrightdescription() + +returns description of a specified source copyright id + +=cut + +sub source_copyrightdescription { + return &mt($scprtag{shift(@_)}); +} + +=pod + +=item * &filecategories() + +returns list of all file categories + +=cut + sub filecategories { - return sort(keys(%fc)); + return sort(keys(%category_extensions)); } -# -------------------------------------- File Types within a specified category +=pod + +=item * &filecategorytypes() + +returns list of file types belonging to a given file +category + +=cut + sub filecategorytypes { - return @{$fc{lc(shift(@_))}}; + my ($cat) = @_; + if (ref($category_extensions{lc($cat)}) eq 'ARRAY') { + return @{$category_extensions{lc($cat)}}; + } else { + return (); + } } -# ------------------------------------------------------------------ File Types -sub fileextensions { - return sort(keys(%fe)); -} +=pod + +=item * &fileembstyle() + +returns embedding style for a specified file type + +=cut -# ------------------------------------------------------------- Embedding Style sub fileembstyle { return $fe{lc(shift(@_))}; } -# ------------------------------------------------------------ Description Text +sub filemimetype { + return $fm{lc(shift(@_))}; +} + + +sub filecategoryselect { + my ($name,$value)=@_; + return &select_form($value,$name, + {'' => &mt('Any category'), map { $_,$_ } sort(keys(%category_extensions))}); +} + +=pod + +=item * &filedescription() + +returns description for a specified file type + +=cut + sub filedescription { - return $fd{lc(shift(@_))}; + my $file_description = $fd{lc(shift())}; + $file_description =~ s:([\[\]]):~$1:g; + return &mt($file_description); } -# ------------------------------------------------------------ Description Text +=pod + +=item * &filedescriptionex() + +returns description for a specified file type with +extra formatting + +=cut + sub filedescriptionex { my $ex=shift; - return '.'.$ex.' '.$fd{lc($ex)}; + my $file_description = $fd{lc($ex)}; + $file_description =~ s:([\[\]]):~$1:g; + return '.'.$ex.' '.&mt($file_description); +} + +# End of .tab access +=pod + +=back + +=cut + +# ------------------------------------------------------------------ File Types +sub fileextensions { + return sort(keys(%fe)); } +# ----------------------------------------------------------- Display Languages +# returns a hash with all desired display languages +# + +sub display_languages { + my %languages=(); + foreach my $lang (&Apache::lonlocal::preferred_languages()) { + $languages{$lang}=1; + } + &get_unprocessed_cgi($ENV{'QUERY_STRING'},['displaylanguage']); + if ($env{'form.displaylanguage'}) { + foreach my $lang (split(/\s*(\,|\;|\:)\s*/,$env{'form.displaylanguage'})) { + $languages{$lang}=1; + } + } + return %languages; +} + +sub languages { + my ($possible_langs) = @_; + my @preferred_langs = &Apache::lonlocal::preferred_languages(); + if (!ref($possible_langs)) { + if( wantarray ) { + return @preferred_langs; + } else { + return $preferred_langs[0]; + } + } + my %possibilities = map { $_ => 1 } (@$possible_langs); + my @preferred_possibilities; + foreach my $preferred_lang (@preferred_langs) { + if (exists($possibilities{$preferred_lang})) { + push(@preferred_possibilities, $preferred_lang); + } + } + if( wantarray ) { + return @preferred_possibilities; + } + return $preferred_possibilities[0]; +} + +sub user_lang { + my ($touname,$toudom,$fromcid) = @_; + my @userlangs; + if (($fromcid ne '') && ($env{'course.'.$fromcid.'.languages'} ne '')) { + @userlangs=(@userlangs,split(/\s*(\,|\;|\:)\s*/, + $env{'course.'.$fromcid.'.languages'})); + } else { + my %langhash = &getlangs($touname,$toudom); + if ($langhash{'languages'} ne '') { + @userlangs = split(/\s*(\,|\;|\:)\s*/,$langhash{'languages'}); + } else { + my %domdefs = &Apache::lonnet::get_domain_defaults($toudom); + if ($domdefs{'lang_def'} ne '') { + @userlangs = ($domdefs{'lang_def'}); + } + } + } + my @languages=&Apache::lonlocal::get_genlanguages(@userlangs); + my $user_lh = Apache::localize->get_handle(@languages); + return $user_lh; +} + + +############################################################### +## Student Answer Attempts ## +############################################################### + +=pod + +=head1 Alternate Problem Views + +=over 4 + +=item * &get_previous_attempt($symb, $username, $domain, $course, + $getattempt, $regexp, $gradesub, $usec, $identifier) + +Return string with previous attempt on problem. Arguments: + +=over 4 + +=item * $symb: Problem, including path + +=item * $username: username of the desired student + +=item * $domain: domain of the desired student + +=item * $course: Course ID + +=item * $getattempt: Leave blank for all attempts, otherwise put + something + +=item * $regexp: if string matches this regexp, the string will be + sent to $gradesub + +=item * $gradesub: routine that processes the string if it matches $regexp + +=item * $usec: section of the desired student + +=item * $identifier: counter for student (multiple students one problem) or + problem (one student; whole sequence). + +=back + +The output string is a table containing all desired attempts, if any. + +=cut + sub get_previous_attempt { - my ($symb,$username,$domain,$course)=@_; + my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub,$usec,$identifier)=@_; my $prevattempts=''; + no strict 'refs'; if ($symb) { my (%returnhash)= &Apache::lonnet::restore($symb,$course,$domain,$username); @@ -267,67 +4366,311 @@ sub get_previous_attempt { my %lasthash=(); my $version; for ($version=1;$version<=$returnhash{'version'};$version++) { - foreach (sort(split(/\:/,$returnhash{$version.':keys'}))) { - $lasthash{$_}=$returnhash{$version.':'.$_}; + foreach my $key (reverse(sort(split(/\:/,$returnhash{$version.':keys'})))) { + if ($key =~ /\.rawrndseed$/) { + my ($id) = ($key =~ /^(.+)\.rawrndseed$/); + $lasthash{$id.'.rndseed'} = $returnhash{$version.':'.$key}; + } else { + $lasthash{$key}=$returnhash{$version.':'.$key}; + } } } - $prevattempts=''; - foreach (sort(keys %lasthash)) { - my ($ign,@parts) = split(/\./,$_); - if (@parts) { + $prevattempts=&start_data_table().&start_data_table_header_row(); + $prevattempts.=''; + my (%typeparts,%lasthidden,%regraded,%hidestatus); + my $showsurv=&Apache::lonnet::allowed('vas',$env{'request.course.id'}); + foreach my $key (sort(keys(%lasthash))) { + my ($ign,@parts) = split(/\./,$key); + if ($#parts > 0) { my $data=$parts[-1]; + next if ($data eq 'foilorder'); pop(@parts); - $prevattempts.=''; + $prevattempts.=''; + if ($data eq 'type') { + unless ($showsurv) { + my $id = join(',',@parts); + $typeparts{$ign.'.'.$id} = $lasthash{$key}; + if (($lasthash{$key} eq 'anonsurvey') || ($lasthash{$key} eq 'anonsurveycred')) { + $lasthidden{$ign.'.'.$id} = 1; + } + } + if ($identifier ne '') { + my $id = join(',',@parts); + if (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb, + $domain,$username,$usec,undef,$course) =~ /^no/) { + $hidestatus{$ign.'.'.$id} = 1; + } + } + } elsif ($data eq 'regrader') { + if (($identifier ne '') && (@parts)) { + my $id = join(',',@parts); + $regraded{$ign.'.'.$id} = 1; + } + } } else { - $prevattempts.=''; - } - } - for ($version=1;$version<=$returnhash{'version'};$version++) { - $prevattempts.=''; - foreach (sort(keys %lasthash)) { - my $value; - if ($_ =~ /timestamp/) { - $value=scalar(localtime($returnhash{$version.':'.$_})); + if ($#parts == 0) { + $prevattempts.=''; } else { - $value=$returnhash{$version.':'.$_}; + $prevattempts.=''; } - $prevattempts.=''; + } + } + $prevattempts.=&end_data_table_header_row(); + if ($getattempt eq '') { + my (%solved,%resets,%probstatus); + if (($identifier ne '') && (keys(%regraded) > 0)) { + for ($version=1;$version<=$returnhash{'version'};$version++) { + foreach my $id (keys(%regraded)) { + if (($returnhash{$version.':'.$id.'.regrader'}) && + ($returnhash{$version.':'.$id.'.tries'} eq '') && + ($returnhash{$version.':'.$id.'.award'} eq '')) { + push(@{$resets{$id}},$version); + } + } + } } + for ($version=1;$version<=$returnhash{'version'};$version++) { + my (@hidden,@unsolved); + if (%typeparts) { + foreach my $id (keys(%typeparts)) { + if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || + ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) { + push(@hidden,$id); + } elsif ($identifier ne '') { + unless (($returnhash{$version.':'.$id.'.type'} eq 'survey') || + ($returnhash{$version.':'.$id.'.type'} eq 'surveycred') || + ($hidestatus{$id})) { + next if ((ref($resets{$id}) eq 'ARRAY') && grep(/^\Q$version\E$/,@{$resets{$id}})); + if ($returnhash{$version.':'.$id.'.solved'} eq 'correct_by_student') { + push(@{$solved{$id}},$version); + } elsif (($returnhash{$version.':'.$id.'.solved'} ne '') && + (ref($solved{$id}) eq 'ARRAY')) { + my $skip; + if (ref($resets{$id}) eq 'ARRAY') { + foreach my $reset (@{$resets{$id}}) { + if ($reset > $solved{$id}[-1]) { + $skip=1; + last; + } + } + } + unless ($skip) { + my ($ign,$partslist) = split(/\./,$id,2); + push(@unsolved,$partslist); + } + } + } + } + } + } + $prevattempts.=&start_data_table_row(). + ''; + if (@hidden) { + foreach my $key (sort(keys(%lasthash))) { + next if ($key =~ /\.foilorder$/); + my $hide; + foreach my $id (@hidden) { + if ($key =~ /^\Q$id\E/) { + $hide = 1; + last; + } + } + if ($hide) { + my ($id,$data) = ($key =~ /^(.+)\.([^.]+)$/); + if (($data eq 'award') || ($data eq 'awarddetail')) { + my $value = &format_previous_attempt_value($key, + $returnhash{$version.':'.$key}); + $prevattempts.=''; + } else { + $prevattempts.=''; + } + } else { + if ($key =~ /\./) { + my $value = $returnhash{$version.':'.$key}; + if ($key =~ /\.rndseed$/) { + my ($id) = ($key =~ /^(.+)\.[^.]+$/); + if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) { + $value = $returnhash{$version.':'.$id.'.rawrndseed'}; + } + } + $prevattempts.=''; + } else { + $prevattempts.=''; + } + } + } + } else { + foreach my $key (sort(keys(%lasthash))) { + next if ($key =~ /\.foilorder$/); + my $value = $returnhash{$version.':'.$key}; + if ($key =~ /\.rndseed$/) { + my ($id) = ($key =~ /^(.+)\.[^.]+$/); + if (exists($returnhash{$version.':'.$id.'.rawrndseed'})) { + $value = $returnhash{$version.':'.$id.'.rawrndseed'}; + } + } + $prevattempts.=''; + } + } + $prevattempts.=&end_data_table_row(); + } } - $prevattempts.=''; - foreach (sort(keys %lasthash)) { - my $value; - if ($_ =~ /timestamp/) { - $value=scalar(localtime($lasthash{$_})); - } else { - $value=$lasthash{$_}; - } - $prevattempts.=''; + my @currhidden = keys(%lasthidden); + $prevattempts.=&start_data_table_row().''; + foreach my $key (sort(keys(%lasthash))) { + next if ($key =~ /\.foilorder$/); + if (%typeparts) { + my $hidden; + foreach my $id (@currhidden) { + if ($key =~ /^\Q$id\E/) { + $hidden = 1; + last; + } + } + if ($hidden) { + my ($id,$data) = ($key =~ /^(.+)\.([^.]+)$/); + if (($data eq 'award') || ($data eq 'awarddetail')) { + my $value = &format_previous_attempt_value($key,$lasthash{$key}); + if ($key =~/$regexp$/ && (defined &$gradesub)) { + $value = &$gradesub($value); + } + $prevattempts.=''; + } else { + $prevattempts.=''; + } + } else { + my $value = &format_previous_attempt_value($key,$lasthash{$key}); + if ($key =~/$regexp$/ && (defined &$gradesub)) { + $value = &$gradesub($value); + } + $prevattempts.=''; + } + } else { + my $value = &format_previous_attempt_value($key,$lasthash{$key}); + if ($key =~/$regexp$/ && (defined &$gradesub)) { + $value = &$gradesub($value); + } + $prevattempts.=''; + } } - $prevattempts.='
History'.&mt('History').'Part '.join('.',@parts).'
'.$data.'
'.&mt('Part ').join('.',@parts).'
'.$data.' 
'.$ign.'
Attempt '.$version.''.$parts[0].''.$ign.''.$value.''.&mt('Transaction [_1]',$version); + if (@unsolved) { + $prevattempts .= ''; + } + $prevattempts .= ''.$value.'  '.&format_previous_attempt_value($key,$value). + '  '.&format_previous_attempt_value($key,$value). + ' 
Current'.$value.''.&mt('Current').''. $value.'  '.$value.' '.$value.' 
'; + $prevattempts.= &end_data_table_row().&end_data_table(); } else { - $prevattempts='Nothing submitted - no attempts.'; + $prevattempts= + &start_data_table().&start_data_table_row(). + ''.&mt('Nothing submitted - no attempts.').''. + &end_data_table_row().&end_data_table(); } } else { - $prevattempts='No data.'; + $prevattempts= + &start_data_table().&start_data_table_row(). + ''.&mt('No data.').''. + &end_data_table_row().&end_data_table(); } } +sub format_previous_attempt_value { + my ($key,$value) = @_; + if (($key =~ /timestamp/) || ($key=~/duedate/)) { + $value = &Apache::lonlocal::locallocaltime($value); + } elsif (ref($value) eq 'ARRAY') { + $value = &HTML::Entities::encode('('.join(', ', @{ $value }).')','"<>&'); + } elsif ($key =~ /answerstring$/) { + my %answers = &Apache::lonnet::str2hash($value); + my @answer = %answers; + %answers = map {&HTML::Entities::encode($_, '"<>&')} @answer; + my @anskeys = sort(keys(%answers)); + if (@anskeys == 1) { + my $answer = $answers{$anskeys[0]}; + if ($answer =~ m{\0}) { + $answer =~ s{\0}{,}g; + } + my $tag_internal_answer_name = 'INTERNAL'; + if ($anskeys[0] eq $tag_internal_answer_name) { + $value = $answer; + } else { + $value = $anskeys[0].'='.$answer; + } + } else { + foreach my $ans (@anskeys) { + my $answer = $answers{$ans}; + if ($answer =~ m{\0}) { + $answer =~ s{\0}{,}g; + } + $value .= $ans.'='.$answer.'
';; + } + } + } else { + $value = &HTML::Entities::encode(&unescape($value), '"<>&'); + } + return $value; +} + + +sub relative_to_absolute { + my ($url,$output)=@_; + my $parser=HTML::TokeParser->new(\$output); + my $token; + my $thisdir=$url; + my @rlinks=(); + while ($token=$parser->get_token) { + if ($token->[0] eq 'S') { + if ($token->[1] eq 'a') { + if ($token->[2]->{'href'}) { + $rlinks[$#rlinks+1]=$token->[2]->{'href'}; + } + } elsif ($token->[1] eq 'img' || $token->[1] eq 'embed' ) { + $rlinks[$#rlinks+1]=$token->[2]->{'src'}; + } elsif ($token->[1] eq 'base') { + $thisdir=$token->[2]->{'href'}; + } + } + } + $thisdir=~s-/[^/]*$--; + foreach my $link (@rlinks) { + unless (($link=~/^https?\:\/\//i) || + ($link=~/^\//) || + ($link=~/^javascript:/i) || + ($link=~/^mailto:/i) || + ($link=~/^\#/)) { + my $newlocation=&Apache::lonnet::hreflocation($thisdir,$link); + $output=~s/(\"|\'|\=\s*)\Q$link\E(\"|\'|\s|\>)/$1$newlocation$2/; + } + } +# -------------------------------------------------- Deal with Applet codebases + $output=~s/(\]+)(codebase\=[^\S\>]+)*([^\>]*)\>/$1.($2?$2:' codebase="'.$thisdir.'"').$3.'>'/gei; + return $output; +} + +=pod + +=item * &get_student_view() + +show a snapshot of what student was looking at + +=cut + sub get_student_view { - my ($symb,$username,$domain,$courseid) = @_; - my ($map,$id,$feedurl) = split(/___/,$symb); - my (%old,%moreenv); + my ($symb,$username,$domain,$courseid,$target,$moreenv) = @_; + my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb); + my (%form); my @elements=('symb','courseid','domain','username'); foreach my $element (@elements) { - $old{$element}=$ENV{'form.grade_'.$element}; - $moreenv{'form.grade_'.$element}=eval '$'.$element #' + $form{'grade_'.$element}=eval '$'.$element #' } - &Apache::lonnet::appenv(%moreenv); - my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); - &Apache::lonnet::delenv('form.grade_'); - foreach my $element (@elements) { - $ENV{'form.grade_'.$element}=$old{$element}; + if (defined($moreenv)) { + %form=(%form,%{$moreenv}); } + if (defined($target)) { $form{'grade_target'} = $target; } + $feedurl=&Apache::lonnet::clutter($feedurl); + my ($userview,$response)=&Apache::lonnet::ssi_body($feedurl,%form); $userview=~s/\]*\>//gi; $userview=~s/\<\/body\>//gi; $userview=~s/\//gi; @@ -335,391 +4678,12860 @@ sub get_student_view { $userview=~s/\//gi; $userview=~s/\<\/head\>//gi; $userview=~s/action\s*\=/would_be_action\=/gi; - return $userview; + $userview=&relative_to_absolute($feedurl,$userview); + if (wantarray) { + return ($userview,$response); + } else { + return $userview; + } +} + +sub get_student_view_with_retries { + my ($symb,$retries,$username,$domain,$courseid,$target,$moreenv) = @_; + + my $ok = 0; # True if we got a good response. + my $content; + my $response; + + # Try to get the student_view done. within the retries count: + + do { + ($content, $response) = &get_student_view($symb,$username,$domain,$courseid,$target,$moreenv); + $ok = $response->is_success; + if (!$ok) { + &Apache::lonnet::logthis("Failed get_student_view_with_retries on $symb: ".$response->is_success.', '.$response->code.', '.$response->message); + } + $retries--; + } while (!$ok && ($retries > 0)); + + if (!$ok) { + $content = ''; # On error return an empty content. + } + if (wantarray) { + return ($content, $response); + } else { + return $content; + } } +=pod + +=item * &get_student_answers() + +show a snapshot of how student was answering problem + +=cut + sub get_student_answers { - my ($symb,$username,$domain,$courseid) = @_; - my ($map,$id,$feedurl) = split(/___/,$symb); - my (%old,%moreenv); + my ($symb,$username,$domain,$courseid,%form) = @_; + my ($map,$id,$feedurl) = &Apache::lonnet::decode_symb($symb); + my (%moreenv); my @elements=('symb','courseid','domain','username'); foreach my $element (@elements) { - $old{$element}=$ENV{'form.grade_'.$element}; - $moreenv{'form.grade_'.$element}=eval '$'.$element #' - } - $moreenv{'form.grade_target'}='answer'; - &Apache::lonnet::appenv(%moreenv); - my $userview=&Apache::lonnet::ssi('/res/'.$feedurl); - &Apache::lonnet::delenv('form.grade_'); - foreach my $element (@elements) { - $ENV{'form.grade_'.$element}=$old{$element}; + $moreenv{'grade_'.$element}=eval '$'.$element #' } - $userview=~s/\]*\>//gi; - $userview=~s/\<\/body\>//gi; - $userview=~s/\//gi; - $userview=~s/\<\/html\>//gi; - $userview=~s/\//gi; - $userview=~s/\<\/head\>//gi; - $userview=~s/action\s*\=/would_be_action\=/gi; + $moreenv{'grade_target'}='answer'; + %moreenv=(%form,%moreenv); + $feedurl = &Apache::lonnet::clutter($feedurl); + my $userview=&Apache::lonnet::ssi($feedurl,%moreenv); return $userview; } +=pod + +=item * &submlink() + +Inputs: $text $uname $udom $symb $target + +Returns: A link to grades.pm such as to see the SUBM view of a student + +=cut + +############################################### +sub submlink { + my ($text,$uname,$udom,$symb,$target)=@_; + if (!($uname && $udom)) { + (my $cursymb, my $courseid,$udom,$uname)= + &Apache::lonnet::whichuser($symb); + if (!$symb) { $symb=$cursymb; } + } + if (!$symb) { $symb=&Apache::lonnet::symbread(); } + $symb=&escape($symb); + if ($target) { $target=" target=\"$target\""; } + return + ''.$text.''; +} +############################################## + +=pod + +=item * &pgrdlink() + +Inputs: $text $uname $udom $symb $target + +Returns: A link to grades.pm such as to see the PGRD view of a student + +=cut + +############################################### +sub pgrdlink { + my $link=&submlink(@_); + $link=~s/(&command=submission)/$1&showgrading=yes/; + return $link; +} +############################################## + +=pod + +=item * &pprmlink() + +Inputs: $text $uname $udom $symb $target + +Returns: A link to parmset.pm such as to see the PPRM view of a +student and a specific resource + +=cut + +############################################### +sub pprmlink { + my ($text,$uname,$udom,$symb,$target)=@_; + if (!($uname && $udom)) { + (my $cursymb, my $courseid,$udom,$uname)= + &Apache::lonnet::whichuser($symb); + if (!$symb) { $symb=$cursymb; } + } + if (!$symb) { $symb=&Apache::lonnet::symbread(); } + $symb=&escape($symb); + if ($target) { $target="target=\"$target\""; } + return ''.$text.''; +} +############################################## + +=pod + +=back + +=cut + +############################################### + + +sub timehash { + my ($thistime) = @_; + my $timezone = &Apache::lonlocal::gettimezone(); + my $dt = DateTime->from_epoch(epoch => $thistime) + ->set_time_zone($timezone); + my $wday = $dt->day_of_week(); + if ($wday == 7) { $wday = 0; } + return ( 'second' => $dt->second(), + 'minute' => $dt->minute(), + 'hour' => $dt->hour(), + 'day' => $dt->day_of_month(), + 'month' => $dt->month(), + 'year' => $dt->year(), + 'weekday' => $wday, + 'dayyear' => $dt->day_of_year(), + 'dlsav' => $dt->is_dst() ); +} + +sub utc_string { + my ($date)=@_; + return strftime("%Y%m%dT%H%M%SZ",gmtime($date)); +} + +sub maketime { + my %th=@_; + my ($epoch_time,$timezone,$dt); + $timezone = &Apache::lonlocal::gettimezone(); + eval { + $dt = DateTime->new( year => $th{'year'}, + month => $th{'month'}, + day => $th{'day'}, + hour => $th{'hour'}, + minute => $th{'minute'}, + second => $th{'second'}, + time_zone => $timezone, + ); + }; + if (!$@) { + $epoch_time = $dt->epoch; + if ($epoch_time) { + return $epoch_time; + } + } + return POSIX::mktime( + ($th{'seconds'},$th{'minutes'},$th{'hours'}, + $th{'day'},$th{'month'}-1,$th{'year'}-1900,0,0,-1)); +} + +######################################### + +sub findallcourses { + my ($roles,$uname,$udom) = @_; + my %roles; + if (ref($roles)) { %roles = map { $_ => 1 } @{$roles}; } + my %courses; + my $now=time; + if (!defined($uname)) { + $uname = $env{'user.name'}; + } + if (!defined($udom)) { + $udom = $env{'user.domain'}; + } + if (($uname ne $env{'user.name'}) || ($udom ne $env{'user.domain'})) { + my %roleshash = &Apache::lonnet::dump('roles',$udom,$uname); + if (!%roles) { + %roles = ( + cc => 1, + co => 1, + in => 1, + ep => 1, + ta => 1, + cr => 1, + st => 1, + ); + } + foreach my $entry (keys(%roleshash)) { + my ($trole,$tend,$tstart) = split(/_/,$roleshash{$entry}); + if ($trole =~ /^cr/) { + next if (!exists($roles{$trole}) && !exists($roles{'cr'})); + } else { + next if (!exists($roles{$trole})); + } + if ($tend) { + next if ($tend < $now); + } + if ($tstart) { + next if ($tstart > $now); + } + my ($cdom,$cnum,$sec,$cnumpart,$secpart,$role); + (undef,$cdom,$cnumpart,$secpart) = split(/\//,$entry); + my $value = $trole.'/'.$cdom.'/'; + if ($secpart eq '') { + ($cnum,$role) = split(/_/,$cnumpart); + $sec = 'none'; + $value .= $cnum.'/'; + } else { + $cnum = $cnumpart; + ($sec,$role) = split(/_/,$secpart); + $value .= $cnum.'/'.$sec; + } + if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') { + unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) { + push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value); + } + } else { + @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value); + } + } + } else { + foreach my $key (keys(%env)) { + if ( $key=~m{^user\.role\.(\w+)\./($match_domain)/($match_courseid)/?(\w*)$} || + $key=~m{^user\.role\.(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_courseid)/?(\w*)$}) { + my ($role,$cdom,$cnum,$sec) = ($1,$2,$3,$4); + next if ($role eq 'ca' || $role eq 'aa'); + next if (%roles && !exists($roles{$role})); + my ($starttime,$endtime)=split(/\./,$env{$key}); + my $active=1; + if ($starttime) { + if ($now<$starttime) { $active=0; } + } + if ($endtime) { + if ($now>$endtime) { $active=0; } + } + if ($active) { + my $value = $role.'/'.$cdom.'/'.$cnum.'/'; + if ($sec eq '') { + $sec = 'none'; + } else { + $value .= $sec; + } + if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') { + unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) { + push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value); + } + } else { + @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value); + } + } + } + } + } + return %courses; +} + +############################################### + +sub blockcheck { + my ($setters,$activity,$uname,$udom,$url,$is_course) = @_; + + if (defined($udom) && defined($uname)) { + # If uname and udom are for a course, check for blocks in the course. + if (($is_course) || (&Apache::lonnet::is_course($udom,$uname))) { + my ($startblock,$endblock,$triggerblock) = + &get_blocks($setters,$activity,$udom,$uname,$url); + return ($startblock,$endblock,$triggerblock); + } + } else { + $udom = $env{'user.domain'}; + $uname = $env{'user.name'}; + } + + my $startblock = 0; + my $endblock = 0; + my $triggerblock = ''; + my %live_courses = &findallcourses(undef,$uname,$udom); + + # If uname is for a user, and activity is course-specific, i.e., + # boards, chat or groups, check for blocking in current course only. + + if (($activity eq 'boards' || $activity eq 'chat' || + $activity eq 'groups' || $activity eq 'printout' || + $activity eq 'reinit' || $activity eq 'alert') && + ($env{'request.course.id'})) { + foreach my $key (keys(%live_courses)) { + if ($key ne $env{'request.course.id'}) { + delete($live_courses{$key}); + } + } + } + + my $otheruser = 0; + my %own_courses; + if ((($uname ne $env{'user.name'})) || ($udom ne $env{'user.domain'})) { + # Resource belongs to user other than current user. + $otheruser = 1; + # Gather courses for current user + %own_courses = + &findallcourses(undef,$env{'user.name'},$env{'user.domain'}); + } + + # Gather active course roles - course coordinator, instructor, + # exam proctor, ta, student, or custom role. + + foreach my $course (keys(%live_courses)) { + my ($cdom,$cnum); + if ((defined($env{'course.'.$course.'.domain'})) && (defined($env{'course.'.$course.'.num'}))) { + $cdom = $env{'course.'.$course.'.domain'}; + $cnum = $env{'course.'.$course.'.num'}; + } else { + ($cdom,$cnum) = split(/_/,$course); + } + my $no_ownblock = 0; + my $no_userblock = 0; + if ($otheruser && $activity ne 'com') { + # Check if current user has 'evb' priv for this + if (defined($own_courses{$course})) { + foreach my $sec (keys(%{$own_courses{$course}})) { + my $checkrole = 'cm./'.$cdom.'/'.$cnum; + if ($sec ne 'none') { + $checkrole .= '/'.$sec; + } + if (&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) { + $no_ownblock = 1; + last; + } + } + } + # if they have 'evb' priv and are currently not playing student + next if (($no_ownblock) && + ($env{'request.role'} !~ m{^st\./$cdom/$cnum})); + } + foreach my $sec (keys(%{$live_courses{$course}})) { + my $checkrole = 'cm./'.$cdom.'/'.$cnum; + if ($sec ne 'none') { + $checkrole .= '/'.$sec; + } + if ($otheruser) { + # Resource belongs to user other than current user. + # Assemble privs for that user, and check for 'evb' priv. + my (%allroles,%userroles); + if (ref($live_courses{$course}{$sec}) eq 'ARRAY') { + foreach my $entry (@{$live_courses{$course}{$sec}}) { + my ($trole,$tdom,$tnum,$tsec); + if ($entry =~ /^cr/) { + ($trole,$tdom,$tnum,$tsec) = + ($entry =~ m|^(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_username)/?(\w*)$|); + } else { + ($trole,$tdom,$tnum,$tsec) = split(/\//,$entry); + } + my ($spec,$area,$trest); + $area = '/'.$tdom.'/'.$tnum; + $trest = $tnum; + if ($tsec ne '') { + $area .= '/'.$tsec; + $trest .= '/'.$tsec; + } + $spec = $trole.'.'.$area; + if ($trole =~ /^cr/) { + &Apache::lonnet::custom_roleprivs(\%allroles,$trole, + $tdom,$spec,$trest,$area); + } else { + &Apache::lonnet::standard_roleprivs(\%allroles,$trole, + $tdom,$spec,$trest,$area); + } + } + my ($author,$adv,$rar) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles); + if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) { + if ($1) { + $no_userblock = 1; + last; + } + } + } + } else { + # Resource belongs to current user + # Check for 'evb' priv via lonnet::allowed(). + if (&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) { + $no_ownblock = 1; + last; + } + } + } + # if they have the evb priv and are currently not playing student + next if (($no_ownblock) && + ($env{'request.role'} !~ m{^st\./\Q$cdom\E/\Q$cnum\E})); + next if ($no_userblock); + + # Retrieve blocking times and identity of locker for course + # of specified user, unless user has 'evb' privilege. + + my ($start,$end,$trigger) = + &get_blocks($setters,$activity,$cdom,$cnum,$url); + if (($start != 0) && + (($startblock == 0) || ($startblock > $start))) { + $startblock = $start; + if ($trigger ne '') { + $triggerblock = $trigger; + } + } + if (($end != 0) && + (($endblock == 0) || ($endblock < $end))) { + $endblock = $end; + if ($trigger ne '') { + $triggerblock = $trigger; + } + } + } + return ($startblock,$endblock,$triggerblock); +} + +sub get_blocks { + my ($setters,$activity,$cdom,$cnum,$url) = @_; + my $startblock = 0; + my $endblock = 0; + my $triggerblock = ''; + my $course = $cdom.'_'.$cnum; + $setters->{$course} = {}; + $setters->{$course}{'staff'} = []; + $setters->{$course}{'times'} = []; + $setters->{$course}{'triggers'} = []; + my (@blockers,%triggered); + my $now = time; + my %commblocks = &Apache::lonnet::get_comm_blocks($cdom,$cnum); + if ($activity eq 'docs') { + @blockers = &Apache::lonnet::has_comm_blocking('bre',undef,$url,\%commblocks); + foreach my $block (@blockers) { + if ($block =~ /^firstaccess____(.+)$/) { + my $item = $1; + my $type = 'map'; + my $timersymb = $item; + if ($item eq 'course') { + $type = 'course'; + } elsif ($item =~ /___\d+___/) { + $type = 'resource'; + } else { + $timersymb = &Apache::lonnet::symbread($item); + } + my $start = $env{'course.'.$cdom.'_'.$cnum.'.firstaccess.'.$timersymb}; + my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb}; + $triggered{$block} = { + start => $start, + end => $end, + type => $type, + }; + } + } + } else { + foreach my $block (keys(%commblocks)) { + if ($block =~ m/^(\d+)____(\d+)$/) { + my ($start,$end) = ($1,$2); + if ($start <= time && $end >= time) { + if (ref($commblocks{$block}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if ($commblocks{$block}{'blocks'}{$activity} eq 'on') { + unless(grep(/^\Q$block\E$/,@blockers)) { + push(@blockers,$block); + } + } + } + } + } + } elsif ($block =~ /^firstaccess____(.+)$/) { + my $item = $1; + my $timersymb = $item; + my $type = 'map'; + if ($item eq 'course') { + $type = 'course'; + } elsif ($item =~ /___\d+___/) { + $type = 'resource'; + } else { + $timersymb = &Apache::lonnet::symbread($item); + } + my $start = $env{'course.'.$cdom.'_'.$cnum.'.firstaccess.'.$timersymb}; + my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb}; + if ($start && $end) { + if (($start <= time) && ($end >= time)) { + if (ref($commblocks{$block}) eq 'HASH') { + if (ref($commblocks{$block}{'blocks'}) eq 'HASH') { + if ($commblocks{$block}{'blocks'}{$activity} eq 'on') { + unless(grep(/^\Q$block\E$/,@blockers)) { + push(@blockers,$block); + $triggered{$block} = { + start => $start, + end => $end, + type => $type, + }; + } + } + } + } + } + } + } + } + } + foreach my $blocker (@blockers) { + my ($staff_name,$staff_dom,$title,$blocks) = + &parse_block_record($commblocks{$blocker}); + push(@{$$setters{$course}{'staff'}},[$staff_name,$staff_dom]); + my ($start,$end,$triggertype); + if ($blocker =~ m/^(\d+)____(\d+)$/) { + ($start,$end) = ($1,$2); + } elsif (ref($triggered{$blocker}) eq 'HASH') { + $start = $triggered{$blocker}{'start'}; + $end = $triggered{$blocker}{'end'}; + $triggertype = $triggered{$blocker}{'type'}; + } + if ($start) { + push(@{$$setters{$course}{'times'}}, [$start,$end]); + if ($triggertype) { + push(@{$$setters{$course}{'triggers'}},$triggertype); + } else { + push(@{$$setters{$course}{'triggers'}},0); + } + if ( ($startblock == 0) || ($startblock > $start) ) { + $startblock = $start; + if ($triggertype) { + $triggerblock = $blocker; + } + } + if ( ($endblock == 0) || ($endblock < $end) ) { + $endblock = $end; + if ($triggertype) { + $triggerblock = $blocker; + } + } + } + } + return ($startblock,$endblock,$triggerblock); +} + +sub parse_block_record { + my ($record) = @_; + my ($setuname,$setudom,$title,$blocks); + if (ref($record) eq 'HASH') { + ($setuname,$setudom) = split(/:/,$record->{'setter'}); + $title = &unescape($record->{'event'}); + $blocks = $record->{'blocks'}; + } else { + my @data = split(/:/,$record,3); + if (scalar(@data) eq 2) { + $title = $data[1]; + ($setuname,$setudom) = split(/@/,$data[0]); + } else { + ($setuname,$setudom,$title) = @data; + } + $blocks = { 'com' => 'on' }; + } + return ($setuname,$setudom,$title,$blocks); +} + +sub blocking_status { + my ($activity,$uname,$udom,$url,$is_course) = @_; + my %setters; + +# check for active blocking + my ($startblock,$endblock,$triggerblock) = + &blockcheck(\%setters,$activity,$uname,$udom,$url,$is_course); + my $blocked = 0; + if ($startblock && $endblock) { + $blocked = 1; + } + +# caller just wants to know whether a block is active + if (!wantarray) { return $blocked; } + +# build a link to a popup window containing the details + my $querystring = "?activity=$activity"; +# $uname and $udom decide whose portfolio the user is trying to look at + if (($activity eq 'port') || ($activity eq 'passwd')) { + $querystring .= "&udom=$udom" if ($udom =~ /^$match_domain$/); + $querystring .= "&uname=$uname" if ($uname =~ /^$match_username$/); + } elsif ($activity eq 'docs') { + $querystring .= '&url='.&HTML::Entities::encode($url,'&"'); + } + + my $output .= <<'END_MYBLOCK'; +function openWindow(url, wdwName, w, h, toolbar,scrollbar) { + var options = "width=" + w + ",height=" + h + ","; + options += "resizable=yes,scrollbars="+scrollbar+",status=no,"; + options += "menubar=no,toolbar="+toolbar+",location=no,directories=no"; + var newWin = window.open(url, wdwName, options); + newWin.focus(); +} +END_MYBLOCK + + $output = Apache::lonhtmlcommon::scripttag($output); + + my $popupUrl = "/adm/blockingstatus/$querystring"; + my $text = &mt('Communication Blocked'); + my $class = 'LC_comblock'; + if ($activity eq 'docs') { + $text = &mt('Content Access Blocked'); + $class = ''; + } elsif ($activity eq 'printout') { + $text = &mt('Printing Blocked'); + } elsif ($activity eq 'passwd') { + $text = &mt('Password Changing Blocked'); + } elsif ($activity eq 'alert') { + $text = &mt('Checking Critical Messages Blocked'); + } elsif ($activity eq 'reinit') { + $text = &mt('Checking Course Update Blocked'); + } + $output .= <<"END_BLOCK"; +
+ + $text + $text +
+ +END_BLOCK + + return ($blocked, $output); +} + +############################################### + +sub check_ip_acc { + my ($acc,$clientip)=@_; + &Apache::lonxml::debug("acc is $acc"); + if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) { + return 1; + } + my $allowed; + my $ip=$ENV{'REMOTE_ADDR'} || $clientip || $env{'request.host'}; + + my $name; + my %access = ( + allowfrom => 1, + denyfrom => 0, + ); + my @allows; + my @denies; + foreach my $item (split(',',$acc)) { + $item =~ s/^\s*//; + $item =~ s/\s*$//; + my $pattern; + if ($item =~ /^\!(.+)$/) { + push(@denies,$1); + } else { + push(@allows,$item); + } + } + my $numdenies = scalar(@denies); + my $numallows = scalar(@allows); + my $count = 0; + foreach my $pattern (@denies,@allows) { + $count ++; + my $acctype = 'allowfrom'; + if ($count <= $numdenies) { + $acctype = 'denyfrom'; + } + if ($pattern =~ /\*$/) { + #35.8.* + $pattern=~s/\*//; + if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; } + } elsif ($pattern =~ /(\d+\.\d+\.\d+)\.\[(\d+)-(\d+)\]$/) { + #35.8.3.[34-56] + my $low=$2; + my $high=$3; + $pattern=$1; + if ($ip =~ /^\Q$pattern\E/) { + my $last=(split(/\./,$ip))[3]; + if ($last <=$high && $last >=$low) { $allowed=$access{$acctype}; } + } + } elsif ($pattern =~ /^\*/) { + #*.msu.edu + $pattern=~s/\*//; + if (!defined($name)) { + use Socket; + my $netaddr=inet_aton($ip); + ($name)=gethostbyaddr($netaddr,AF_INET); + } + if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; } + } elsif ($pattern =~ /\d+\.\d+\.\d+\.\d+/) { + #127.0.0.1 + if ($ip =~ /^\Q$pattern\E/) { $allowed=$access{$acctype}; } + } else { + #some.name.com + if (!defined($name)) { + use Socket; + my $netaddr=inet_aton($ip); + ($name)=gethostbyaddr($netaddr,AF_INET); + } + if ($name =~ /\Q$pattern\E$/i) { $allowed=$access{$acctype}; } + } + if ($allowed =~ /^(0|1)$/) { last; } + } + if ($allowed eq '') { + if ($numdenies && !$numallows) { + $allowed = 1; + } else { + $allowed = 0; + } + } + return $allowed; +} + +############################################### + +=pod + +=head1 Domain Template Functions + +=over 4 + +=item * &determinedomain() + +Inputs: $domain (usually will be undef) + +Returns: Determines which domain should be used for designs + +=cut + +############################################### +sub determinedomain { + my $domain=shift; + if (! $domain) { + # Determine domain if we have not been given one + $domain = &Apache::lonnet::default_login_domain(); + if ($env{'user.domain'}) { $domain=$env{'user.domain'}; } + if ($env{'request.role.domain'}) { + $domain=$env{'request.role.domain'}; + } + } + return $domain; +} +############################################### + +sub devalidate_domconfig_cache { + my ($udom)=@_; + &Apache::lonnet::devalidate_cache_new('domainconfig',$udom); +} + +# ---------------------- Get domain configuration for a domain +sub get_domainconf { + my ($udom) = @_; + my $cachetime=1800; + my ($result,$cached)=&Apache::lonnet::is_cached_new('domainconfig',$udom); + if (defined($cached)) { return %{$result}; } + + my %domconfig = &Apache::lonnet::get_dom('configuration', + ['login','rolecolors','autoenroll'],$udom); + my (%designhash,%legacy); + if (keys(%domconfig) > 0) { + if (ref($domconfig{'login'}) eq 'HASH') { + if (keys(%{$domconfig{'login'}})) { + foreach my $key (keys(%{$domconfig{'login'}})) { + if (ref($domconfig{'login'}{$key}) eq 'HASH') { + if (($key eq 'loginvia') || ($key eq 'headtag')) { + if (ref($domconfig{'login'}{$key}) eq 'HASH') { + foreach my $hostname (keys(%{$domconfig{'login'}{$key}})) { + if (ref($domconfig{'login'}{$key}{$hostname}) eq 'HASH') { + if ($key eq 'loginvia') { + if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) { + my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'}; + $designhash{$udom.'.login.loginvia'} = $server; + if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') { + + $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'}; + } else { + $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'}; + } + } + } elsif ($key eq 'headtag') { + if ($domconfig{'login'}{'headtag'}{$hostname}{'url'}) { + $designhash{$udom.'.login.headtag_'.$hostname} = $domconfig{'login'}{'headtag'}{$hostname}{'url'}; + } + } + if ($domconfig{'login'}{$key}{$hostname}{'exempt'}) { + $designhash{$udom.'.login.'.$key.'_exempt_'.$hostname} = $domconfig{'login'}{$key}{$hostname}{'exempt'}; + } + } + } + } + } else { + foreach my $img (keys(%{$domconfig{'login'}{$key}})) { + $designhash{$udom.'.login.'.$key.'_'.$img} = + $domconfig{'login'}{$key}{$img}; + } + } + } else { + $designhash{$udom.'.login.'.$key}=$domconfig{'login'}{$key}; + } + } + } else { + $legacy{'login'} = 1; + } + } else { + $legacy{'login'} = 1; + } + if (ref($domconfig{'rolecolors'}) eq 'HASH') { + if (keys(%{$domconfig{'rolecolors'}})) { + foreach my $role (keys(%{$domconfig{'rolecolors'}})) { + if (ref($domconfig{'rolecolors'}{$role}) eq 'HASH') { + foreach my $item (keys(%{$domconfig{'rolecolors'}{$role}})) { + $designhash{$udom.'.'.$role.'.'.$item}=$domconfig{'rolecolors'}{$role}{$item}; + } + } + } + } else { + $legacy{'rolecolors'} = 1; + } + } else { + $legacy{'rolecolors'} = 1; + } + if (ref($domconfig{'autoenroll'}) eq 'HASH') { + if ($domconfig{'autoenroll'}{'co-owners'}) { + $designhash{$udom.'.autoassign.co-owners'}=$domconfig{'autoenroll'}{'co-owners'}; + } + } + if (keys(%legacy) > 0) { + my %legacyhash = &get_legacy_domconf($udom); + foreach my $item (keys(%legacyhash)) { + if ($item =~ /^\Q$udom\E\.login/) { + if ($legacy{'login'}) { + $designhash{$item} = $legacyhash{$item}; + } + } else { + if ($legacy{'rolecolors'}) { + $designhash{$item} = $legacyhash{$item}; + } + } + } + } + } else { + %designhash = &get_legacy_domconf($udom); + } + &Apache::lonnet::do_cache_new('domainconfig',$udom,\%designhash, + $cachetime); + return %designhash; +} + +sub get_legacy_domconf { + my ($udom) = @_; + my %legacyhash; + my $designdir=$Apache::lonnet::perlvar{'lonTabDir'}.'/lonDomColors'; + my $designfile = $designdir.'/'.$udom.'.tab'; + if (-e $designfile) { + if ( open (my $fh,"<$designfile") ) { + while (my $line = <$fh>) { + next if ($line =~ /^\#/); + chomp($line); + my ($key,$val)=(split(/\=/,$line)); + if ($val) { $legacyhash{$udom.'.'.$key}=$val; } + } + close($fh); + } + } + if (-e $Apache::lonnet::perlvar{'lonDocRoot'}.'/adm/lonDomLogos/'.$udom.'.gif') { + $legacyhash{$udom.'.login.domlogo'} = "/adm/lonDomLogos/$udom.gif"; + } + return %legacyhash; +} + +=pod + +=item * &domainlogo() + +Inputs: $domain (usually will be undef) + +Returns: A link to a domain logo, if the domain logo exists. +If the domain logo does not exist, a description of the domain. + +=cut + +############################################### +sub domainlogo { + my $domain = &determinedomain(shift); + my %designhash = &get_domainconf($domain); + # See if there is a logo + if ($designhash{$domain.'.login.domlogo'} ne '') { + my $imgsrc = $designhash{$domain.'.login.domlogo'}; + if ($imgsrc =~ m{^/(adm|res)/}) { + if ($imgsrc =~ m{^/res/}) { + my $local_name = &Apache::lonnet::filelocation('',$imgsrc); + &Apache::lonnet::repcopy($local_name); + } + $imgsrc = &lonhttpdurl($imgsrc); + } + return ''.$domain.''; + } elsif (defined(&Apache::lonnet::domain($domain,'description'))) { + return &Apache::lonnet::domain($domain,'description'); + } else { + return ''; + } +} +############################################## + +=pod + +=item * &designparm() + +Inputs: $which parameter; $domain (usually will be undef) + +Returns: value of designparamter $which + +=cut + + +############################################## +sub designparm { + my ($which,$domain)=@_; + if (exists($env{'environment.color.'.$which})) { + return $env{'environment.color.'.$which}; + } + $domain=&determinedomain($domain); + my %domdesign; + unless ($domain eq 'public') { + %domdesign = &get_domainconf($domain); + } + my $output; + if ($domdesign{$domain.'.'.$which} ne '') { + $output = $domdesign{$domain.'.'.$which}; + } else { + $output = $defaultdesign{$which}; + } + if (($which =~ /^(student|coordinator|author|admin)\.img$/) || + ($which =~ /login\.(img|logo|domlogo|login)/)) { + if ($output =~ m{^/(adm|res)/}) { + if ($output =~ m{^/res/}) { + my $local_name = &Apache::lonnet::filelocation('',$output); + &Apache::lonnet::repcopy($local_name); + } + $output = &lonhttpdurl($output); + } + } + return $output; +} + +############################################## +=pod + +=item * &authorspace() + +Inputs: $url (usually will be undef). + +Returns: Path to Authoring Space containing the resource or + directory being viewed (or for which action is being taken). + If $url is provided, and begins /priv// + the path will be that portion of the $context argument. + Otherwise the path will be for the author space of the current + user when the current role is author, or for that of the + co-author/assistant co-author space when the current role + is co-author or assistant co-author. + +=cut + +sub authorspace { + my ($url) = @_; + if ($url ne '') { + if ($url =~ m{^(/priv/$match_domain/$match_username/)}) { + return $1; + } + } + my $caname = ''; + my $cadom = ''; + if ($env{'request.role'} =~ /^(?:ca|aa)/) { + ($cadom,$caname) = + ($env{'request.role'}=~/($match_domain)\/($match_username)$/); + } elsif ($env{'request.role'} =~ m{^au\./($match_domain)/}) { + $caname = $env{'user.name'}; + $cadom = $env{'user.domain'}; + } + if (($caname ne '') && ($cadom ne '')) { + return "/priv/$cadom/$caname/"; + } + return; +} + +############################################## +=pod + +=item * &head_subbox() + +Inputs: $content (contains HTML code with page functions, etc.) + +Returns: HTML div with $content + To be included in page header + +=cut + +sub head_subbox { + my ($content)=@_; + my $output = + '
' + .$content + .'
' +} + +############################################## +=pod + +=item * &CSTR_pageheader() + +Input: (optional) filename from which breadcrumb trail is built. + In most cases no input as needed, as $env{'request.filename'} + is appropriate for use in building the breadcrumb trail. + +Returns: HTML div with CSTR path and recent box + To be included on Authoring Space pages + +=cut + +sub CSTR_pageheader { + my ($trailfile) = @_; + if ($trailfile eq '') { + $trailfile = $env{'request.filename'}; + } + +# this is for resources; directories have customtitle, and crumbs +# and select recent are created in lonpubdir.pm + + my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; + my ($udom,$uname,$thisdisfn)= + ($trailfile =~ m{^\Q$londocroot\E/priv/([^/]+)/([^/]+)(?:|/(.*))$}); + my $formaction = "/priv/$udom/$uname/$thisdisfn"; + $formaction =~ s{/+}{/}g; + + my $parentpath = ''; + my $lastitem = ''; + if ($thisdisfn =~ m-(.+/)([^/]*)$-) { + $parentpath = $1; + $lastitem = $2; + } else { + $lastitem = $thisdisfn; + } + + my ($crsauthor,$title); + if (($env{'request.course.id'}) && + ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) && + ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) { + $crsauthor = 1; + $title = &mt('Course Authoring Space'); + } else { + $title = &mt('Authoring Space'); + } + + my $output = + '
' + .&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it? + .''.$title.' ' + .'' #FIXME lonpubdir: target="_parent" + .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,'_top','/priv/'.$udom,undef,undef); + + if ($lastitem) { + $output .= + '' + .$lastitem + .''; + } + + if ($crsauthor) { + $output .= ''.&Apache::lonmenu::constspaceform(); + } else { + $output .= + '
' + #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/','_top','/priv','','+1',1)."
" + .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()') + .'' + .&Apache::lonmenu::constspaceform(); + } + $output .= '
'; + + return $output; +} + +############################################### +############################################### + +=pod + +=back + +=head1 HTML Helpers + +=over 4 + +=item * &bodytag() + +Returns a uniform header for LON-CAPA web pages. + +Inputs: + +=over 4 + +=item * $title, A title to be displayed on the page. + +=item * $function, the current role (can be undef). + +=item * $addentries, extra parameters for the tag. + +=item * $bodyonly, if defined, only return the tag. + +=item * $domain, if defined, force a given domain. + +=item * $forcereg, if page should register as content page (relevant for + text interface only) + +=item * $no_nav_bar, if true, keep the 'what is this' info but remove the + navigational links + +=item * $bgcolor, used to override the bgcolor on a webpage to a specific value + +=item * $args, optional argument valid values are + no_auto_mt_title -> prevents &mt()ing the title arg + use_absolute -> for external resource or syllabus, this will + contain https:// if server uses + https (as per hosts.tab), but request is for http + hostname -> hostname, from $r->hostname(). + +=item * $advtoolsref, optional argument, ref to an array containing + inlineremote items to be added in "Functions" menu below + breadcrumbs. + +=back + +Returns: A uniform header for LON-CAPA web pages. +If $bodyonly is nonzero, a string containing a tag will be returned. +If $bodyonly is undef or zero, an html string containing a tag and +other decorations will be returned. + +=cut + +sub bodytag { + my ($title,$function,$addentries,$bodyonly,$domain,$forcereg, + $no_nav_bar,$bgcolor,$args,$advtoolsref)=@_; + + my $public; + if ((($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public')) + || ($env{'user.name'} eq '') && ($env{'user.domain'} eq '')) { + $public = 1; + } + if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); } + my $httphost = $args->{'use_absolute'}; + my $hostname = $args->{'hostname'}; + + $function = &get_users_function() if (!$function); + my $img = &designparm($function.'.img',$domain); + my $font = &designparm($function.'.font',$domain); + my $pgbg = $bgcolor || &designparm($function.'.pgbg',$domain); + + my %design = ( 'style' => 'margin-top: 0', + 'bgcolor' => $pgbg, + 'text' => $font, + 'alink' => &designparm($function.'.alink',$domain), + 'vlink' => &designparm($function.'.vlink',$domain), + 'link' => &designparm($function.'.link',$domain),); + @design{keys(%$addentries)} = @$addentries{keys(%$addentries)}; + + # role and realm + my ($role,$realm) = split(m{\./},$env{'request.role'},2); + if ($realm) { + $realm = '/'.$realm; + } + if ($role eq 'ca') { + my ($rdom,$rname) = ($realm =~ m{^/($match_domain)/($match_username)$}); + $realm = &plainname($rname,$rdom); + } +# realm + if ($env{'request.course.id'}) { + if ($env{'request.role'} !~ /^cr/) { + $role = &Apache::lonnet::plaintext($role,&course_type()); + } elsif ($role =~ m{^cr/($match_domain)/\1-domainconfig/(\w+)$}) { + if ($env{'request.role.desc'}) { + $role = $env{'request.role.desc'}; + } else { + $role = &mt('Helpdesk[_1]',' '.$2); + } + } else { + $role = (split(/\//,$role,4))[-1]; + } + if ($env{'request.course.sec'}) { + $role .= (' 'x2).'- '.&mt('section:').' '.$env{'request.course.sec'}; + } + $realm = $env{'course.'.$env{'request.course.id'}.'.description'}; + } else { + $role = &Apache::lonnet::plaintext($role); + } + + if (!$realm) { $realm=' '; } + + my $extra_body_attr = &make_attr_string($forcereg,\%design); + +# construct main body tag + my $bodytag = "". + &Apache::lontexconvert::init_math_support(); + + &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']); + + if (($bodyonly) || ($no_nav_bar) || ($env{'form.inhibitmenu'} eq 'yes')) { + return $bodytag; + } + + if ($public) { + undef($role); + } + + my $titleinfo = '

'.$title.'

'; + # + # Extra info if you are the DC + my $dc_info = ''; + if ($env{'user.adv'} && exists($env{'user.role.dc./'. + $env{'course.'.$env{'request.course.id'}. + '.domain'}.'/'})) { + my $cid = $env{'request.course.id'}; + $dc_info = $cid.' '.$env{'course.'.$cid.'.internal.coursecode'}; + $dc_info =~ s/\s+$//; + } + + my $crstype; + if ($env{'request.course.id'}) { + $crstype = $env{'course.'.$env{'request.course.id'}.'.type'}; + } elsif ($args->{'crstype'}) { + $crstype = $args->{'crstype'}; + } + if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) { + undef($role); + } else { + $role = '('.$role.')' if ($role && !$env{'browser.mobile'}); + } + + if ($env{'request.state'} eq 'construct') { $forcereg=1; } + + # if ($env{'request.state'} eq 'construct') { + # $titleinfo = &CSTR_pageheader(); #FIXME: Will be removed once all scripts have their own calls + # } + + $bodytag .= Apache::lonhtmlcommon::scripttag( + Apache::lonmenu::utilityfunctions($httphost), 'start'); + + my ($left,$right) = Apache::lonmenu::primary_menu($crstype); + + if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) { + if ($dc_info) { + $dc_info = qq|$dc_info|; + } + $bodytag .= qq|
$left $role
+ $realm $dc_info
|; + return $bodytag; + } + + unless ($env{'request.symb'} =~ m/\.page___\d+___/) { + $bodytag .= qq|
$left $role
|; + } + + $bodytag .= $right; + + if ($dc_info) { + $dc_info = &dc_courseid_toggle($dc_info); + } + $bodytag .= qq|
$realm $dc_info
|; + + #if directed to not display the secondary menu, don't. + if ($args->{'no_secondary_menu'}) { + return $bodytag; + } + #don't show menus for public users + if (!$public){ + $bodytag .= Apache::lonmenu::secondary_menu($httphost); + $bodytag .= Apache::lonmenu::serverform(); + $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + if ($env{'request.state'} eq 'construct') { + $bodytag .= &Apache::lonmenu::innerregister($forcereg, + $args->{'bread_crumbs'},'','',$hostname); + } elsif ($forcereg) { + $bodytag .= &Apache::lonmenu::innerregister($forcereg,undef, + $args->{'group'}, + $args->{'hide_buttons'}, + $hostname); + } else { + $bodytag .= + &Apache::lonmenu::prepare_functions($env{'request.noversionuri'}, + $forcereg,$args->{'group'}, + $args->{'bread_crumbs'}, + $advtoolsref,'',$hostname); + } + }else{ + # this is to seperate menu from content when there's no secondary + # menu. Especially needed for public accessible ressources. + $bodytag .= '
'; + $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + } + + return $bodytag; +} + +sub dc_courseid_toggle { + my ($dc_info) = @_; + return ' '. + ''. + &mt('(More ...)').''. + '
'.$dc_info.'
'; +} + +sub make_attr_string { + my ($register,$attr_ref) = @_; + + if ($attr_ref && !ref($attr_ref)) { + die("addentries Must be a hash ref ". + join(':',caller(1))." ". + join(':',caller(0))." "); + } + + if ($register) { + my ($on_load,$on_unload); + foreach my $key (keys(%{$attr_ref})) { + if (lc($key) eq 'onload') { + $on_load.=$attr_ref->{$key}.';'; + delete($attr_ref->{$key}); + + } elsif (lc($key) eq 'onunload') { + $on_unload.=$attr_ref->{$key}.';'; + delete($attr_ref->{$key}); + } + } + $attr_ref->{'onload'} = $on_load; + $attr_ref->{'onunload'}= $on_unload; + } + + my $attr_string; + foreach my $attr (sort(keys(%$attr_ref))) { + $attr_string .= " $attr=\"".$attr_ref->{$attr}.'" '; + } + return $attr_string; +} + + +############################################### +############################################### + +=pod + +=item * &endbodytag() + +Returns a uniform footer for LON-CAPA web pages. + +Inputs: 1 - optional reference to an args hash +If in the hash, key for noredirectlink has a value which evaluates to true, +a 'Continue' link is not displayed if the page contains an +internal redirect in the section, +i.e., $env{'internal.head.redirect'} exists + +=cut + +sub endbodytag { + my ($args) = @_; + my $endbodytag; + unless ((ref($args) eq 'HASH') && ($args->{'notbody'})) { + $endbodytag=''; + } + if ( exists( $env{'internal.head.redirect'} ) ) { + if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) { + $endbodytag= + "
". + &mt('Continue').''. + $endbodytag; + } + } + return $endbodytag; +} + +=pod + +=item * &standard_css() + +Returns a style sheet + +Inputs: (all optional) + domain -> force to color decorate a page for a specific + domain + function -> force usage of a specific rolish color scheme + bgcolor -> override the default page bgcolor + +=cut + +sub standard_css { + my ($function,$domain,$bgcolor) = @_; + $function = &get_users_function() if (!$function); + my $img = &designparm($function.'.img', $domain); + my $tabbg = &designparm($function.'.tabbg', $domain); + my $font = &designparm($function.'.font', $domain); + my $fontmenu = &designparm($function.'.fontmenu', $domain); +#second colour for later usage + my $sidebg = &designparm($function.'.sidebg',$domain); + my $pgbg_or_bgcolor = + $bgcolor || + &designparm($function.'.pgbg', $domain); + my $pgbg = &designparm($function.'.pgbg', $domain); + my $alink = &designparm($function.'.alink', $domain); + my $vlink = &designparm($function.'.vlink', $domain); + my $link = &designparm($function.'.link', $domain); + + my $sans = 'Verdana,Arial,Helvetica,sans-serif'; + my $mono = 'monospace'; + my $data_table_head = $sidebg; + my $data_table_light = '#FAFAFA'; + my $data_table_dark = '#E0E0E0'; + my $data_table_darker = '#CCCCCC'; + my $data_table_highlight = '#FFFF00'; + my $mail_new = '#FFBB77'; + my $mail_new_hover = '#DD9955'; + my $mail_read = '#BBBB77'; + my $mail_read_hover = '#999944'; + my $mail_replied = '#AAAA88'; + my $mail_replied_hover = '#888855'; + my $mail_other = '#99BBBB'; + my $mail_other_hover = '#669999'; + my $table_header = '#DDDDDD'; + my $feedback_link_bg = '#BBBBBB'; + my $lg_border_color = '#C8C8C8'; + my $button_hover = '#BF2317'; + + my $border = ($env{'browser.type'} eq 'explorer' || + $env{'browser.type'} eq 'safari' ) ? '0 2px 0 2px' + : '0 3px 0 4px'; + + + return < td { + background-color: #CCCCCC; + font-weight: bold; + text-align: left; +} + +table.LC_data_table tr.LC_odd_row > td { + background-color: $data_table_light; + padding: 2px; + vertical-align: top; +} + +table.LC_pick_box tr > td.LC_odd_row { + background-color: $data_table_light; + vertical-align: top; +} + +table.LC_data_table tr.LC_even_row > td { + background-color: $data_table_dark; + padding: 2px; + vertical-align: top; +} + +table.LC_pick_box tr > td.LC_even_row { + background-color: $data_table_dark; + vertical-align: top; +} + +table.LC_data_table tr.LC_data_table_highlight td { + background-color: $data_table_darker; +} + +table.LC_data_table tr td.LC_leftcol_header { + background-color: $data_table_head; + font-weight: bold; +} + +table.LC_data_table tr.LC_empty_row td, +table.LC_nested tr.LC_empty_row td { + font-weight: bold; + font-style: italic; + text-align: center; + padding: 8px; +} + +table.LC_data_table tr.LC_empty_row td, +table.LC_data_table tr.LC_footer_row td { + background-color: $sidebg; +} + +table.LC_nested tr.LC_empty_row td { + background-color: #FFFFFF; +} + +table.LC_caption { +} + +table.LC_nested tr.LC_empty_row td { + padding: 4ex +} + +table.LC_nested_outer tr th { + font-weight: bold; + color:$fontmenu; + background-color: $data_table_head; + font-size: small; + border-bottom: 1px solid #000000; +} + +table.LC_nested_outer tr td.LC_subheader { + background-color: $data_table_head; + font-weight: bold; + font-size: small; + border-bottom: 1px solid #000000; + text-align: right; +} + +table.LC_nested tr.LC_info_row td { + background-color: #CCCCCC; + font-weight: bold; + font-size: small; + text-align: center; +} + +table.LC_nested tr.LC_info_row td.LC_left_item, +table.LC_nested_outer tr th.LC_left_item { + text-align: left; +} + +table.LC_nested td { + background-color: #FFFFFF; + font-size: small; +} + +table.LC_nested_outer tr th.LC_right_item, +table.LC_nested tr.LC_info_row td.LC_right_item, +table.LC_nested tr.LC_odd_row td.LC_right_item, +table.LC_nested tr td.LC_right_item { + text-align: right; +} + +table.LC_nested tr.LC_odd_row td { + background-color: #EEEEEE; +} + +table.LC_createuser { +} + +table.LC_createuser tr.LC_section_row td { + font-size: small; +} + +table.LC_createuser tr.LC_info_row td { + background-color: #CCCCCC; + font-weight: bold; + text-align: center; +} + +table.LC_calendar { + border: 1px solid #000000; + border-collapse: collapse; + width: 98%; +} + +table.LC_calendar_pickdate { + font-size: xx-small; +} + +table.LC_calendar tr td { + border: 1px solid #000000; + vertical-align: top; + width: 14%; +} + +table.LC_calendar tr td.LC_calendar_day_empty { + background-color: $data_table_dark; +} + +table.LC_calendar tr td.LC_calendar_day_current { + background-color: $data_table_highlight; +} + +table.LC_data_table tr td.LC_mail_new { + background-color: $mail_new; +} + +table.LC_data_table tr.LC_mail_new:hover { + background-color: $mail_new_hover; +} + +table.LC_data_table tr td.LC_mail_read { + background-color: $mail_read; +} + +/* +table.LC_data_table tr.LC_mail_read:hover { + background-color: $mail_read_hover; +} +*/ + +table.LC_data_table tr td.LC_mail_replied { + background-color: $mail_replied; +} + +/* +table.LC_data_table tr.LC_mail_replied:hover { + background-color: $mail_replied_hover; +} +*/ + +table.LC_data_table tr td.LC_mail_other { + background-color: $mail_other; +} + +/* +table.LC_data_table tr.LC_mail_other:hover { + background-color: $mail_other_hover; +} +*/ + +table.LC_data_table tr > td.LC_browser_file, +table.LC_data_table tr > td.LC_browser_file_published { + background: #AAEE77; +} + +table.LC_data_table tr > td.LC_browser_file_locked, +table.LC_data_table tr > td.LC_browser_file_unpublished { + background: #FFAA99; +} + +table.LC_data_table tr > td.LC_browser_file_obsolete { + background: #888888; +} + +table.LC_data_table tr > td.LC_browser_file_modified, +table.LC_data_table tr > td.LC_browser_file_metamodified { + background: #F8F866; +} + +table.LC_data_table tr.LC_browser_folder > td { + background: #E0E8FF; +} + +table.LC_data_table tr > td.LC_roles_is { + /* background: #77FF77; */ +} + +table.LC_data_table tr > td.LC_roles_future { + border-right: 8px solid #FFFF77; +} + +table.LC_data_table tr > td.LC_roles_will { + border-right: 8px solid #FFAA77; +} + +table.LC_data_table tr > td.LC_roles_expired { + border-right: 8px solid #FF7777; +} + +table.LC_data_table tr > td.LC_roles_will_not { + border-right: 8px solid #AAFF77; +} + +table.LC_data_table tr > td.LC_roles_selected { + border-right: 8px solid #11CC55; +} + +span.LC_current_location { + font-size:larger; + background: $pgbg; +} + +span.LC_current_nav_location { + font-weight:bold; + background: $sidebg; +} + +span.LC_parm_menu_item { + font-size: larger; +} + +span.LC_parm_scope_all { + color: red; +} + +span.LC_parm_scope_folder { + color: green; +} + +span.LC_parm_scope_resource { + color: orange; +} + +span.LC_parm_part { + color: blue; +} + +span.LC_parm_folder, +span.LC_parm_symb { + font-size: x-small; + font-family: $mono; + color: #AAAAAA; +} + +ul.LC_parm_parmlist li { + display: inline-block; + padding: 0.3em 0.8em; + vertical-align: top; + width: 150px; + border-top:1px solid $lg_border_color; +} + +td.LC_parm_overview_level_menu, +td.LC_parm_overview_map_menu, +td.LC_parm_overview_parm_selectors, +td.LC_parm_overview_restrictions { + border: 1px solid black; + border-collapse: collapse; +} + +table.LC_parm_overview_restrictions td { + border-width: 1px 4px 1px 4px; + border-style: solid; + border-color: $pgbg; + text-align: center; +} + +table.LC_parm_overview_restrictions th { + background: $tabbg; + border-width: 1px 4px 1px 4px; + border-style: solid; + border-color: $pgbg; +} + +table#LC_helpmenu { + border: none; + height: 55px; + border-spacing: 0; +} + +table#LC_helpmenu fieldset legend { + font-size: larger; +} + +table#LC_helpmenu_links { + width: 100%; + border: 1px solid black; + background: $pgbg; + padding: 0; + border-spacing: 1px; +} + +table#LC_helpmenu_links tr td { + padding: 1px; + background: $tabbg; + text-align: center; + font-weight: bold; +} + +table#LC_helpmenu_links a:link, +table#LC_helpmenu_links a:visited, +table#LC_helpmenu_links a:active { + text-decoration: none; + color: $font; +} + +table#LC_helpmenu_links a:hover { + text-decoration: underline; + color: $vlink; +} + +.LC_chrt_popup_exists { + border: 1px solid #339933; + margin: -1px; +} + +.LC_chrt_popup_up { + border: 1px solid yellow; + margin: -1px; +} + +.LC_chrt_popup { + border: 1px solid #8888FF; + background: #CCCCFF; +} + +table.LC_pick_box { + border-collapse: separate; + background: white; + border: 1px solid black; + border-spacing: 1px; +} + +table.LC_pick_box td.LC_pick_box_title { + background: $sidebg; + font-weight: bold; + text-align: left; + vertical-align: top; + width: 184px; + padding: 8px; +} + +table.LC_pick_box td.LC_pick_box_value { + text-align: left; + padding: 8px; +} + +table.LC_pick_box td.LC_pick_box_select { + text-align: left; + padding: 8px; +} + +table.LC_pick_box td.LC_pick_box_separator { + padding: 0; + height: 1px; + background: black; +} + +table.LC_pick_box td.LC_pick_box_submit { + text-align: right; +} + +table.LC_pick_box td.LC_evenrow_value { + text-align: left; + padding: 8px; + background-color: $data_table_light; +} + +table.LC_pick_box td.LC_oddrow_value { + text-align: left; + padding: 8px; + background-color: $data_table_light; +} + +span.LC_helpform_receipt_cat { + font-weight: bold; +} + +table.LC_group_priv_box { + background: white; + border: 1px solid black; + border-spacing: 1px; +} + +table.LC_group_priv_box td.LC_pick_box_title { + background: $tabbg; + font-weight: bold; + text-align: right; + width: 184px; +} + +table.LC_group_priv_box td.LC_groups_fixed { + background: $data_table_light; + text-align: center; +} + +table.LC_group_priv_box td.LC_groups_optional { + background: $data_table_dark; + text-align: center; +} + +table.LC_group_priv_box td.LC_groups_functionality { + background: $data_table_darker; + text-align: center; + font-weight: bold; +} + +table.LC_group_priv td { + text-align: left; + padding: 0; +} + +.LC_navbuttons { + margin: 2ex 0ex 2ex 0ex; +} + +.LC_topic_bar { + font-weight: bold; + background: $tabbg; + margin: 1em 0em 1em 2em; + padding: 3px; + font-size: 1.2em; +} + +.LC_topic_bar span { + left: 0.5em; + position: absolute; + vertical-align: middle; + font-size: 1.2em; +} + +table.LC_course_group_status { + margin: 20px; +} + +table.LC_status_selector td { + vertical-align: top; + text-align: center; + padding: 4px; +} + +div.LC_feedback_link { + clear: both; + background: $sidebg; + width: 100%; + padding-bottom: 10px; + border: 1px $tabbg solid; + height: 22px; + line-height: 22px; + padding-top: 5px; +} + +div.LC_feedback_link img { + height: 22px; + vertical-align:middle; +} + +div.LC_feedback_link a { + text-decoration: none; +} + +div.LC_comblock { + display:inline; + color:$font; + font-size:90%; +} + +div.LC_feedback_link div.LC_comblock { + padding-left:5px; +} + +div.LC_feedback_link div.LC_comblock a { + color:$font; +} + +span.LC_feedback_link { + /* background: $feedback_link_bg; */ + font-size: larger; +} + +span.LC_message_link { + /* background: $feedback_link_bg; */ + font-size: larger; + position: absolute; + right: 1em; +} + +table.LC_prior_tries { + border: 1px solid #000000; + border-collapse: separate; + border-spacing: 1px; +} + +table.LC_prior_tries td { + padding: 2px; +} + +.LC_answer_correct { + background: lightgreen; + color: darkgreen; + padding: 6px; +} + +.LC_answer_charged_try { + background: #FFAAAA; + color: darkred; + padding: 6px; +} + +.LC_answer_not_charged_try, +.LC_answer_no_grade, +.LC_answer_late { + background: lightyellow; + color: black; + padding: 6px; +} + +.LC_answer_previous { + background: lightblue; + color: darkblue; + padding: 6px; +} + +.LC_answer_no_message { + background: #FFFFFF; + color: black; + padding: 6px; +} + +.LC_answer_unknown { + background: orange; + color: black; + padding: 6px; +} + +span.LC_prior_numerical, +span.LC_prior_string, +span.LC_prior_custom, +span.LC_prior_reaction, +span.LC_prior_math { + font-family: $mono; + white-space: pre; +} + +span.LC_prior_string { + font-family: $mono; + white-space: pre; +} + +table.LC_prior_option { + width: 100%; + border-collapse: collapse; +} + +table.LC_prior_rank, +table.LC_prior_match { + border-collapse: collapse; +} + +table.LC_prior_option tr td, +table.LC_prior_rank tr td, +table.LC_prior_match tr td { + border: 1px solid #000000; +} + +.LC_nobreak { + white-space: nowrap; +} + +span.LC_cusr_emph { + font-style: italic; +} + +span.LC_cusr_subheading { + font-weight: normal; + font-size: 85%; +} + +div.LC_docs_entry_move { + border: 1px solid #BBBBBB; + background: #DDDDDD; + width: 22px; + padding: 1px; + margin: 0; +} + +table.LC_data_table tr > td.LC_docs_entry_commands, +table.LC_data_table tr > td.LC_docs_entry_parameter { + font-size: x-small; +} + +.LC_docs_entry_parameter { + white-space: nowrap; +} + +.LC_docs_copy { + color: #000099; +} + +.LC_docs_cut { + color: #550044; +} + +.LC_docs_rename { + color: #009900; +} + +.LC_docs_remove { + color: #990000; +} + +.LC_docs_alias { + color: #440055; +} + +.LC_docs_alias_name, +.LC_docs_reinit_warn, +.LC_docs_ext_edit { + font-size: x-small; +} + +table.LC_docs_adddocs td, +table.LC_docs_adddocs th { + border: 1px solid #BBBBBB; + padding: 4px; + background: #DDDDDD; +} + +table.LC_sty_begin { + background: #BBFFBB; +} + +table.LC_sty_end { + background: #FFBBBB; +} + +table.LC_double_column { + border-width: 0; + border-collapse: collapse; + width: 100%; + padding: 2px; +} + +table.LC_double_column tr td.LC_left_col { + top: 2px; + left: 2px; + width: 47%; + vertical-align: top; +} + +table.LC_double_column tr td.LC_right_col { + top: 2px; + right: 2px; + width: 47%; + vertical-align: top; +} + +div.LC_left_float { + float: left; + padding-right: 5%; + padding-bottom: 4px; +} + +div.LC_clear_float_header { + padding-bottom: 2px; +} + +div.LC_clear_float_footer { + padding-top: 10px; + clear: both; +} + +div.LC_grade_show_user { +/* border-left: 5px solid $sidebg; */ + border-top: 5px solid #000000; + margin: 50px 0 0 0; + padding: 15px 0 5px 10px; +} + +div.LC_grade_show_user_odd_row { +/* border-left: 5px solid #000000; */ +} + +div.LC_grade_show_user div.LC_Box { + margin-right: 50px; +} + +div.LC_grade_submissions, +div.LC_grade_message_center, +div.LC_grade_info_links { + margin: 5px; + width: 99%; + background: #FFFFFF; +} + +div.LC_grade_submissions_header, +div.LC_grade_message_center_header { + font-weight: bold; + font-size: large; +} + +div.LC_grade_submissions_body, +div.LC_grade_message_center_body { + border: 1px solid black; + width: 99%; + background: #FFFFFF; +} + +table.LC_scantron_action { + width: 100%; +} + +table.LC_scantron_action tr th { + font-weight:bold; + font-style:normal; +} + +.LC_edit_problem_header, +div.LC_edit_problem_footer { + font-weight: normal; + font-size: medium; + margin: 2px; + background-color: $sidebg; +} + +div.LC_edit_problem_header, +div.LC_edit_problem_header div, +div.LC_edit_problem_footer, +div.LC_edit_problem_footer div, +div.LC_edit_problem_editxml_header, +div.LC_edit_problem_editxml_header div { + z-index: 100; +} + +div.LC_edit_problem_header_title { + font-weight: bold; + font-size: larger; + background: $tabbg; + padding: 3px; + margin: 0 0 5px 0; +} + +table.LC_edit_problem_header_title { + width: 100%; + background: $tabbg; +} + +div.LC_edit_actionbar { + background-color: $sidebg; + margin: 0; + padding: 0; + line-height: 200%; +} + +div.LC_edit_actionbar div{ + padding: 0; + margin: 0; + display: inline-block; +} + +.LC_edit_opt { + padding-left: 1em; + white-space: nowrap; +} + +.LC_edit_problem_latexhelper{ + text-align: right; +} + +#LC_edit_problem_colorful div{ + margin-left: 40px; +} + +#LC_edit_problem_codemirror div{ + margin-left: 0px; +} + +img.stift { + border-width: 0; + vertical-align: middle; +} + +table td.LC_mainmenu_col_fieldset { + vertical-align: top; +} + +div.LC_createcourse { + margin: 10px 10px 10px 10px; +} + +.LC_dccid { + float: right; + margin: 0.2em 0 0 0; + padding: 0; + font-size: 90%; + display:none; +} + +ol.LC_primary_menu a:hover, +ol#LC_MenuBreadcrumbs a:hover, +ol#LC_PathBreadcrumbs a:hover, +ul#LC_secondary_menu a:hover, +.LC_FormSectionClearButton input:hover +ul.LC_TabContent li:hover a { + color:$button_hover; + text-decoration:none; +} + +h1 { + padding: 0; + line-height:130%; +} + +h2, +h3, +h4, +h5, +h6 { + margin: 5px 0 5px 0; + padding: 0; + line-height:130%; +} + +.LC_hcell { + padding:3px 15px 3px 15px; + margin: 0; + background-color:$tabbg; + color:$fontmenu; + border-bottom:solid 1px $lg_border_color; +} + +.LC_Box > .LC_hcell { + margin: 0 -10px 10px -10px; +} + +.LC_noBorder { + border: 0; +} + +.LC_FormSectionClearButton input { + background-color:transparent; + border: none; + cursor:pointer; + text-decoration:underline; +} + +.LC_help_open_topic { + color: #FFFFFF; + background-color: #EEEEFF; + margin: 1px; + padding: 4px; + border: 1px solid #000033; + white-space: nowrap; + /* vertical-align: middle; */ +} + +dl, +ul, +div, +fieldset { + margin: 10px 10px 10px 0; + /* overflow: hidden; */ +} + +article.geogebraweb div { + margin: 0; +} + +fieldset > legend { + font-weight: bold; + padding: 0 5px 0 5px; +} + +#LC_nav_bar { + float: left; + background-color: $pgbg_or_bgcolor; + margin: 0 0 2px 0; +} + +#LC_realm { + margin: 0.2em 0 0 0; + padding: 0; + font-weight: bold; + text-align: center; + background-color: $pgbg_or_bgcolor; +} + +#LC_nav_bar em { + font-weight: bold; + font-style: normal; +} + +ol.LC_primary_menu { + margin: 0; + padding: 0; +} + +ol#LC_PathBreadcrumbs { + margin: 0; +} + +ol.LC_primary_menu li { + color: RGB(80, 80, 80); + vertical-align: middle; + text-align: left; + list-style: none; + position: relative; + float: left; + z-index: 100; /* will be displayed above codemirror and underneath the help-layer */ + line-height: 1.5em; +} + +ol.LC_primary_menu li a, +ol.LC_primary_menu li p { + display: block; + margin: 0; + padding: 0 5px 0 10px; + text-decoration: none; +} + +ol.LC_primary_menu li p span.LC_primary_menu_innertitle { + display: inline-block; + width: 95%; + text-align: left; +} + +ol.LC_primary_menu li p span.LC_primary_menu_innerarrow { + display: inline-block; + width: 5%; + float: right; + text-align: right; + font-size: 70%; +} + +ol.LC_primary_menu ul { + display: none; + width: 15em; + background-color: $data_table_light; + position: absolute; + top: 100%; +} + +ol.LC_primary_menu ul ul { + left: 100%; + top: 0; +} + +ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul { + display: block; + position: absolute; + margin: 0; + padding: 0; + z-index: 2; +} + +ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li { +/* First Submenu -> size should be smaller than the menu title of the whole menu */ + font-size: 90%; + vertical-align: top; + float: none; + border-left: 1px solid black; + border-right: 1px solid black; +/* A dark bottom border to visualize different menu options; +overwritten in the create_submenu routine for the last border-bottom of the menu */ + border-bottom: 1px solid $data_table_dark; +} + +ol.LC_primary_menu li li p:hover { + color:$button_hover; + text-decoration:none; + background-color:$data_table_dark; +} + +ol.LC_primary_menu li li a:hover { + color:$button_hover; + background-color:$data_table_dark; +} + +/* Font-size equal to the size of the predecessors*/ +ol.LC_primary_menu li:hover li li { + font-size: 100%; +} + +ol.LC_primary_menu li img { + vertical-align: bottom; + height: 1.1em; + margin: 0.2em 0 0 0; +} + +ol.LC_primary_menu a { + color: RGB(80, 80, 80); + text-decoration: none; +} + +ol.LC_primary_menu a.LC_new_message { + font-weight:bold; + color: darkred; +} + +ol.LC_docs_parameters { + margin-left: 0; + padding: 0; + list-style: none; +} + +ol.LC_docs_parameters li { + margin: 0; + padding-right: 20px; + display: inline; +} + +ol.LC_docs_parameters li:before { + content: "\\002022 \\0020"; +} + +li.LC_docs_parameters_title { + font-weight: bold; +} + +ol.LC_docs_parameters li.LC_docs_parameters_title:before { + content: ""; +} + +ul#LC_secondary_menu { + clear: right; + color: $fontmenu; + background: $tabbg; + list-style: none; + padding: 0; + margin: 0; + width: 100%; + text-align: left; + float: left; +} + +ul#LC_secondary_menu li { + font-weight: bold; + line-height: 1.8em; + border-right: 1px solid black; + float: left; +} + +ul#LC_secondary_menu li.LC_hoverable:hover, ul#LC_secondary_menu li.hover { + background-color: $data_table_light; +} + +ul#LC_secondary_menu li a { + padding: 0 0.8em; +} + +ul#LC_secondary_menu li ul { + display: none; +} + +ul#LC_secondary_menu li:hover ul, ul#LC_secondary_menu li.hover ul { + display: block; + position: absolute; + margin: 0; + padding: 0; + list-style:none; + float: none; + background-color: $data_table_light; + z-index: 2; + margin-left: -1px; +} + +ul#LC_secondary_menu li ul li { + font-size: 90%; + vertical-align: top; + border-left: 1px solid black; + border-right: 1px solid black; + background-color: $data_table_light; + list-style:none; + float: none; +} + +ul#LC_secondary_menu li ul li:hover, ul#LC_secondary_menu li ul li.hover { + background-color: $data_table_dark; +} + +ul.LC_TabContent { + display:block; + background: $sidebg; + border-bottom: solid 1px $lg_border_color; + list-style:none; + margin: -1px -10px 0 -10px; + padding: 0; +} + +ul.LC_TabContent li, +ul.LC_TabContentBigger li { + float:left; +} + +ul#LC_secondary_menu li a { + color: $fontmenu; + text-decoration: none; +} + +ul.LC_TabContent { + min-height:20px; +} + +ul.LC_TabContent li { + vertical-align:middle; + padding: 0 16px 0 10px; + background-color:$tabbg; + border-bottom:solid 1px $lg_border_color; + border-left: solid 1px $font; +} + +ul.LC_TabContent .right { + float:right; +} + +ul.LC_TabContent li a, +ul.LC_TabContent li { + color:rgb(47,47,47); + text-decoration:none; + font-size:95%; + font-weight:bold; + min-height:20px; +} + +ul.LC_TabContent li a:hover, +ul.LC_TabContent li a:focus { + color: $button_hover; + background:none; + outline:none; +} + +ul.LC_TabContent li:hover { + color: $button_hover; + cursor:pointer; +} + +ul.LC_TabContent li.active { + color: $font; + background:#FFFFFF url(/adm/lonIcons/open.gif) no-repeat scroll right center; + border-bottom:solid 1px #FFFFFF; + cursor: default; +} + +ul.LC_TabContent li.active a { + color:$font; + background:#FFFFFF; + outline: none; +} + +ul.LC_TabContent li.goback { + float: left; + border-left: none; +} + +#maincoursedoc { + clear:both; +} + +ul.LC_TabContentBigger { + display:block; + list-style:none; + padding: 0; +} + +ul.LC_TabContentBigger li { + vertical-align:bottom; + height: 30px; + font-size:110%; + font-weight:bold; + color: #737373; +} + +ul.LC_TabContentBigger li.active { + position: relative; + top: 1px; +} + +ul.LC_TabContentBigger li a { + background:url('/adm/lonIcons/tabbgleft.gif') left bottom no-repeat; + height: 30px; + line-height: 30px; + text-align: center; + display: block; + text-decoration: none; + outline: none; +} + +ul.LC_TabContentBigger li.active a { + background:url('/adm/lonIcons/tabbgleft.gif') left top no-repeat; + color:$font; +} + +ul.LC_TabContentBigger li b { + background: url('/adm/lonIcons/tabbgright.gif') no-repeat right bottom; + display: block; + float: left; + padding: 0 30px; + border-bottom: 1px solid $lg_border_color; +} + +ul.LC_TabContentBigger li:hover b { + color:$button_hover; +} + +ul.LC_TabContentBigger li.active b { + background:url('/adm/lonIcons/tabbgright.gif') right top no-repeat; + color:$font; + border: 0; +} + + +ul.LC_CourseBreadcrumbs { + background: $sidebg; + height: 2em; + padding-left: 10px; + margin: 0; + list-style-position: inside; +} + +ol#LC_MenuBreadcrumbs, +ol#LC_PathBreadcrumbs { + padding-left: 10px; + margin: 0; + height: 2.5em; /* equal to #LC_breadcrumbs line-height */ +} + +ol#LC_MenuBreadcrumbs li, +ol#LC_PathBreadcrumbs li, +ul.LC_CourseBreadcrumbs li { + display: inline; + white-space: normal; +} + +ol#LC_MenuBreadcrumbs li a, +ul.LC_CourseBreadcrumbs li a { + text-decoration: none; + font-size:90%; +} + +ol#LC_MenuBreadcrumbs h1 { + display: inline; + font-size: 90%; + line-height: 2.5em; + margin: 0; + padding: 0; +} + +ol#LC_PathBreadcrumbs li a { + text-decoration:none; + font-size:100%; + font-weight:bold; +} + +.LC_Box { + border: solid 1px $lg_border_color; + padding: 0 10px 10px 10px; +} + +.LC_DocsBox { + border: solid 1px $lg_border_color; + padding: 0 0 10px 10px; +} + +.LC_AboutMe_Image { + float:left; + margin-right:10px; +} + +.LC_Clear_AboutMe_Image { + clear:left; +} + +dl.LC_ListStyleClean dt { + padding-right: 5px; + display: table-header-group; +} + +dl.LC_ListStyleClean dd { + display: table-row; +} + +.LC_ListStyleClean, +.LC_ListStyleSimple, +.LC_ListStyleNormal, +.LC_ListStyleSpecial { + /* display:block; */ + list-style-position: inside; + list-style-type: none; + overflow: hidden; + padding: 0; +} + +.LC_ListStyleSimple li, +.LC_ListStyleSimple dd, +.LC_ListStyleNormal li, +.LC_ListStyleNormal dd, +.LC_ListStyleSpecial li, +.LC_ListStyleSpecial dd { + margin: 0; + padding: 5px 5px 5px 10px; + clear: both; +} + +.LC_ListStyleClean li, +.LC_ListStyleClean dd { + padding-top: 0; + padding-bottom: 0; +} + +.LC_ListStyleSimple dd, +.LC_ListStyleSimple li { + border-bottom: solid 1px $lg_border_color; +} + +.LC_ListStyleSpecial li, +.LC_ListStyleSpecial dd { + list-style-type: none; + background-color: RGB(220, 220, 220); + margin-bottom: 4px; +} + +table.LC_SimpleTable { + margin:5px; + border:solid 1px $lg_border_color; +} + +table.LC_SimpleTable tr { + padding: 0; + border:solid 1px $lg_border_color; +} + +table.LC_SimpleTable thead { + background:rgb(220,220,220); +} + +div.LC_columnSection { + display: block; + clear: both; + overflow: hidden; + margin: 0; +} + +div.LC_columnSection>* { + float: left; + margin: 10px 20px 10px 0; + overflow:hidden; +} + +table em { + font-weight: bold; + font-style: normal; +} + +table.LC_tableBrowseRes, +table.LC_tableOfContent { + border:none; + border-spacing: 1px; + padding: 3px; + background-color: #FFFFFF; + font-size: 90%; +} + +table.LC_tableOfContent { + border-collapse: collapse; +} + +table.LC_tableBrowseRes a, +table.LC_tableOfContent a { + background-color: transparent; + text-decoration: none; +} + +table.LC_tableOfContent img { + border: none; + height: 1.3em; + vertical-align: text-bottom; + margin-right: 0.3em; +} + +a#LC_content_toolbar_firsthomework { + background-image:url(/res/adm/pages/open-first-problem.gif); +} + +a#LC_content_toolbar_everything { + background-image:url(/res/adm/pages/show-all.gif); +} + +a#LC_content_toolbar_uncompleted { + background-image:url(/res/adm/pages/show-incomplete-problems.gif); +} + +#LC_content_toolbar_clearbubbles { + background-image:url(/res/adm/pages/mark-discussionentries-read.gif); +} + +a#LC_content_toolbar_changefolder { + background : url(/res/adm/pages/close-all-folders.gif) top center ; +} + +a#LC_content_toolbar_changefolder_toggled { + background-image:url(/res/adm/pages/open-all-folders.gif); +} + +a#LC_content_toolbar_edittoplevel { + background-image:url(/res/adm/pages/edittoplevel.gif); +} + +ul#LC_toolbar li a:hover { + background-position: bottom center; +} + +ul#LC_toolbar { + padding: 0; + margin: 2px; + list-style:none; + position:relative; + background-color:white; + overflow: auto; +} + +ul#LC_toolbar li { + border:1px solid white; + padding: 0; + margin: 0; + float: left; + display:inline; + vertical-align:middle; + white-space: nowrap; +} + + +a.LC_toolbarItem { + display:block; + padding: 0; + margin: 0; + height: 32px; + width: 32px; + color:white; + border: none; + background-repeat:no-repeat; + background-color:transparent; +} + +ul.LC_funclist { + margin: 0; + padding: 0.5em 1em 0.5em 0; +} + +ul.LC_funclist > li:first-child { + font-weight:bold; + margin-left:0.8em; +} + +ul.LC_funclist + ul.LC_funclist { + /* + left border as a seperator if we have more than + one list + */ + border-left: 1px solid $sidebg; + /* + this hides the left border behind the border of the + outer box if element is wrapped to the next 'line' + */ + margin-left: -1px; +} + +ul.LC_funclist li { + display: inline; + white-space: nowrap; + margin: 0 0 0 25px; + line-height: 150%; +} + +.LC_hidden { + display: none; +} + +.LCmodal-overlay { + position:fixed; + top:0; + right:0; + bottom:0; + left:0; + height:100%; + width:100%; + margin:0; + padding:0; + background:#999; + opacity:.75; + filter: alpha(opacity=75); + -moz-opacity: 0.75; + z-index:101; +} + +* html .LCmodal-overlay { + position: absolute; + height: expression(document.body.scrollHeight > document.body.offsetHeight ? document.body.scrollHeight : document.body.offsetHeight + 'px'); +} + +.LCmodal-window { + position:fixed; + top:50%; + left:50%; + margin:0; + padding:0; + z-index:102; + } + +* html .LCmodal-window { + position:absolute; +} + +.LCclose-window { + position:absolute; + width:32px; + height:32px; + right:8px; + top:8px; + background:transparent url('/res/adm/pages/process-stop.png') no-repeat scroll right top; + text-indent:-99999px; + overflow:hidden; + cursor:pointer; +} + +/* + styles used for response display +*/ +div.LC_radiofoil, div.LC_rankfoil { + margin: .5em 0em .5em 0em; +} +table.LC_itemgroup { + margin-top: 1em; +} + +/* + styles used by TTH when "Default set of options to pass to tth/m + when converting TeX" in course settings has been set + + option passed: -t + +*/ + +td div.comp { margin-top: -0.6ex; margin-bottom: -1ex;} +td div.comb { margin-top: -0.6ex; margin-bottom: -.6ex;} +td div.hrcomp { line-height: 0.9; margin-top: -0.8ex; margin-bottom: -1ex;} +td div.norm {line-height:normal;} + +/* + option passed -y3 +*/ + +span.roman {font-family: serif; font-style: normal; font-weight: normal;} +span.overacc2 {position: relative; left: .8em; top: -1.2ex;} +span.overacc1 {position: relative; left: .6em; top: -1.2ex;} + +/* + sections with roles, for content only +*/ +section[class^="role-"] { + padding-left: 10px; + padding-right: 5px; + margin-top: 8px; + margin-bottom: 8px; + border: 1px solid #2A4; + border-radius: 5px; + box-shadow: 0px 1px 1px #BBB; +} +section[class^="role-"]>h1 { + position: relative; + margin: 0px; + padding-top: 10px; + padding-left: 40px; +} +section[class^="role-"]>h1:before { + position: absolute; + left: -5px; + top: 5px; +} +section.role-activity>h1:before { + content:url('/adm/daxe/images/section_icons/activity.png'); +} +section.role-advice>h1:before { + content:url('/adm/daxe/images/section_icons/advice.png'); +} +section.role-bibliography>h1:before { + content:url('/adm/daxe/images/section_icons/bibliography.png'); +} +section.role-citation>h1:before { + content:url('/adm/daxe/images/section_icons/citation.png'); +} +section.role-conclusion>h1:before { + content:url('/adm/daxe/images/section_icons/conclusion.png'); +} +section.role-definition>h1:before { + content:url('/adm/daxe/images/section_icons/definition.png'); +} +section.role-demonstration>h1:before { + content:url('/adm/daxe/images/section_icons/demonstration.png'); +} +section.role-example>h1:before { + content:url('/adm/daxe/images/section_icons/example.png'); +} +section.role-explanation>h1:before { + content:url('/adm/daxe/images/section_icons/explanation.png'); +} +section.role-introduction>h1:before { + content:url('/adm/daxe/images/section_icons/introduction.png'); +} +section.role-method>h1:before { + content:url('/adm/daxe/images/section_icons/method.png'); +} +section.role-more_information>h1:before { + content:url('/adm/daxe/images/section_icons/more_information.png'); +} +section.role-objectives>h1:before { + content:url('/adm/daxe/images/section_icons/objectives.png'); +} +section.role-prerequisites>h1:before { + content:url('/adm/daxe/images/section_icons/prerequisites.png'); +} +section.role-remark>h1:before { + content:url('/adm/daxe/images/section_icons/remark.png'); +} +section.role-reminder>h1:before { + content:url('/adm/daxe/images/section_icons/reminder.png'); +} +section.role-summary>h1:before { + content:url('/adm/daxe/images/section_icons/summary.png'); +} +section.role-syntax>h1:before { + content:url('/adm/daxe/images/section_icons/syntax.png'); +} +section.role-warning>h1:before { + content:url('/adm/daxe/images/section_icons/warning.png'); +} + +#LC_minitab_header { + float:left; + width:100%; + background:#DAE0D2 url("/res/adm/pages/minitabmenu_bg.gif") repeat-x bottom; + font-size:93%; + line-height:normal; + margin: 0.5em 0 0.5em 0; +} +#LC_minitab_header ul { + margin:0; + padding:10px 10px 0; + list-style:none; +} +#LC_minitab_header li { + float:left; + background:url("/res/adm/pages/minitabmenu_left.gif") no-repeat left top; + margin:0; + padding:0 0 0 9px; +} +#LC_minitab_header a { + display:block; + background:url("/res/adm/pages/minitabmenu_right.gif") no-repeat right top; + padding:5px 15px 4px 6px; +} +#LC_minitab_header #LC_current_minitab { + background-image:url("/res/adm/pages/minitabmenu_left_on.gif"); +} +#LC_minitab_header #LC_current_minitab a { + background-image:url("/res/adm/pages/minitabmenu_right_on.gif"); + padding-bottom:5px; +} + + +END +} + +=pod + +=item * &headtag() + +Returns a uniform footer for LON-CAPA web pages. + +Inputs: $title - optional title for the head + $head_extra - optional extra HTML to put inside the + $args - optional arguments + force_register - if is true call registerurl so the remote is + informed + redirect -> array ref of + 1- seconds before redirect occurs + 2- url to redirect to + 3- whether the side effect should occur + (side effect of setting + $env{'internal.head.redirect'} to the url + redirected too) + domain -> force to color decorate a page for a specific + domain + function -> force usage of a specific rolish color scheme + bgcolor -> override the default page bgcolor + no_auto_mt_title + -> prevent &mt()ing the title arg + +=cut + +sub headtag { + my ($title,$head_extra,$args) = @_; + + my $function = $args->{'function'} || &get_users_function(); + my $domain = $args->{'domain'} || &determinedomain(); + my $bgcolor = $args->{'bgcolor'} || &designparm($function.'.pgbg',$domain); + my $httphost = $args->{'use_absolute'}; + my $url = join(':',$env{'user.name'},$env{'user.domain'}, + $Apache::lonnet::perlvar{'lonVersion'}, + #time(), + $env{'environment.color.timestamp'}, + $function,$domain,$bgcolor); + + $url = '/adm/css/'.&escape($url).'.css'; + + my $result = + ''. + &font_settings($args); + + my $inhibitprint; + if ($args->{'print_suppress'}) { + $inhibitprint = &print_suppression(); + } + + if (!$args->{'frameset'}) { + $result .= &Apache::lonhtmlcommon::htmlareaheaders(); + } + if ($args->{'force_register'} && $env{'request.noversionuri'} !~ m{^/res/adm/pages/}) { + $result .= Apache::lonxml::display_title(); + } + if (!$args->{'no_nav_bar'} + && !$args->{'only_body'} + && !$args->{'frameset'}) { + $result .= &help_menu_js($httphost); + $result.=&modal_window(); + $result.=&togglebox_script(); + $result.=&wishlist_window(); + $result.=&LCprogressbarUpdate_script(); + } else { + if ($args->{'add_modal'}) { + $result.=&modal_window(); + } + if ($args->{'add_wishlist'}) { + $result.=&wishlist_window(); + } + if ($args->{'add_togglebox'}) { + $result.=&togglebox_script(); + } + if ($args->{'add_progressbar'}) { + $result.=&LCprogressbarUpdate_script(); + } + } + if (ref($args->{'redirect'})) { + my ($time,$url,$inhibit_continue) = @{$args->{'redirect'}}; + $url = &Apache::lonenc::check_encrypt($url); + if (!$inhibit_continue) { + $env{'internal.head.redirect'} = $url; + } + $result.=< + +ADDMETA + } else { + unless (($args->{'frameset'}) || ($args->{'js_ready'}) || ($args->{'only_body'}) || ($args->{'no_nav_bar'})) { + my $requrl = $env{'request.uri'}; + if ($requrl eq '') { + $requrl = $ENV{'REQUEST_URI'}; + $requrl =~ s/\?.+$//; + } + unless (($requrl =~ m{^/adm/(?:switchserver|login|authenticate|logout|groupsort|cleanup|helper|slotrequest|grades)(\?|$)}) || + (($requrl =~ m{^/res/}) && (($env{'form.submitted'} eq 'scantron') || + ($env{'form.grade_symb'}) || ($Apache::lonhomework::scantronmode)))) { + my $dom_in_use = $Apache::lonnet::perlvar{'lonDefDomain'}; + unless (&Apache::lonnet::allowed('mau',$dom_in_use)) { + my %domdefs = &Apache::lonnet::get_domain_defaults($dom_in_use); + if (ref($domdefs{'offloadnow'}) eq 'HASH') { + my $lonhost = $Apache::lonnet::perlvar{'lonHostID'}; + if ($domdefs{'offloadnow'}{$lonhost}) { + my $newserver = &Apache::lonnet::spareserver(30000,undef,1,$dom_in_use); + if (($newserver) && ($newserver ne $lonhost)) { + my $numsec = 5; + my $timeout = $numsec * 1000; + my ($newurl,$locknum,%locks,$msg); + if ($env{'request.role.adv'}) { + ($locknum,%locks) = &Apache::lonnet::get_locks(); + } + my $disable_submit = 0; + if ($requrl =~ /$LONCAPA::assess_re/) { + $disable_submit = 1; + } + if ($locknum) { + my @lockinfo = sort(values(%locks)); + $msg = &mt('Once the following tasks are complete: ')."\\n". + join(", ",sort(values(%locks)))."\\n". + &mt('your session will be transferred to a different server, after you click "Roles".'); + } else { + if (($requrl =~ m{^/res/}) && ($env{'form.submitted'} =~ /^part_/)) { + $msg = &mt('Your LON-CAPA submission has been recorded')."\\n"; + } + $msg .= &mt('Your current LON-CAPA session will be transferred to a different server in [quant,_1,second].',$numsec); + $newurl = '/adm/switchserver?otherserver='.$newserver; + if (($env{'request.role'}) && ($env{'request.role'} ne 'cm')) { + $newurl .= '&role='.$env{'request.role'}; + } + if ($env{'request.symb'}) { + $newurl .= '&symb='.$env{'request.symb'}; + } else { + $newurl .= '&origurl='.$requrl; + } + } + &js_escape(\$msg); + $result.=< + +OFFLOAD + } + } + } + } + } + } + } + if (!defined($title)) { + $title = 'The LearningOnline Network with CAPA'; + } + if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); } + $result .= ' LON-CAPA '.$title.'' + .'{'frameset'}) { + $result .= ' /'; + } + $result .= '>' + .$inhibitprint + .$head_extra; + my $clientmobile; + if (($env{'user.name'} eq '') && ($env{'user.domain'} eq '')) { + (undef,undef,undef,undef,undef,undef,$clientmobile) = &decode_user_agent(); + } else { + $clientmobile = $env{'browser.mobile'}; + } + if ($clientmobile) { + $result .= ' + +'; + } + $result .= ''."\n"; + return $result.''; +} + +=pod + +=item * &font_settings() + +Returns neccessary to set the proper encoding + +Inputs: optional reference to HASH -- $args passed to &headtag() + +=cut + +sub font_settings { + my ($args) = @_; + my $headerstring=''; + if ((!$env{'browser.mathml'} && $env{'browser.unicode'}) || + ((ref($args) eq 'HASH') && ($args->{'browser.unicode'}))) { + $headerstring.= + '{'frameset'}) { + $headerstring.= ' /'; + } + $headerstring .= '>'."\n"; + } + return $headerstring; +} + +=pod + +=item * &print_suppression() + +In course context returns css which causes the body to be blank when media="print", +if printout generation is unavailable for the current resource. + +This could be because: + +(a) printstartdate is in the future + +(b) printenddate is in the past + +(c) there is an active exam block with "printout" +functionality blocked + +Users with pav, pfo or evb privileges are exempt. + +Inputs: none + +=cut + + +sub print_suppression { + my $noprint; + if ($env{'request.course.id'}) { + my $scope = $env{'request.course.id'}; + if ((&Apache::lonnet::allowed('pav',$scope)) || + (&Apache::lonnet::allowed('pfo',$scope))) { + return; + } + if ($env{'request.course.sec'} ne '') { + $scope .= "/$env{'request.course.sec'}"; + if ((&Apache::lonnet::allowed('pav',$scope)) || + (&Apache::lonnet::allowed('pfo',$scope))) { + return; + } + } + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $blocked = &blocking_status('printout',$cnum,$cdom,undef,1); + if ($blocked) { + my $checkrole = "cm./$cdom/$cnum"; + if ($env{'request.course.sec'} ne '') { + $checkrole .= "/$env{'request.course.sec'}"; + } + unless ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) && + ($env{'request.role'} !~ m{^st\./$cdom/$cnum})) { + $noprint = 1; + } + } + unless ($noprint) { + my $symb = &Apache::lonnet::symbread(); + if ($symb ne '') { + my $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + my $res = $navmap->getBySymb($symb); + if (ref($res)) { + if (!$res->resprintable()) { + $noprint = 1; + } + } + } + } + } + if ($noprint) { + return <<"ENDSTYLE"; + +ENDSTYLE + } + } + return; +} + +=pod + +=item * &xml_begin() + +Returns the needed doctype and + +Inputs: none + +=cut + +sub xml_begin { + my ($is_frameset) = @_; + my $output=''; + + if ($env{'browser.mathml'}) { + $output='' + #.''."\n" +# .'] >' + .'' + .''; + } elsif ($is_frameset) { + $output=''."\n". + ''."\n"; + } else { + $output=''."\n". + ''."\n"; + } + return $output; +} + +=pod + +=item * &start_page() + +Returns a complete .. section for LON-CAPA web pages. + +Inputs: + +=over 4 + +$title - optional title for the page + +$head_extra - optional extra HTML to incude inside the + +$args - additional optional args supported are: + +=over 8 + + only_body -> is true will set &bodytag() onlybodytag + arg on + no_nav_bar -> is true will set &bodytag() no_nav_bar arg on + add_entries -> additional attributes to add to the + domain -> force to color decorate a page for a + specific domain + function -> force usage of a specific rolish color + scheme + redirect -> see &headtag() + bgcolor -> override the default page bg color + js_ready -> return a string ready for being used in + a javascript writeln + html_encode -> return a string ready for being used in + a html attribute + force_register -> if is true will turn on the &bodytag() + $forcereg arg + frameset -> if true will start with a + rather than + skip_phases -> hash ref of + head -> skip the generation + body -> skip all generation + no_auto_mt_title -> prevent &mt()ing the title arg + bread_crumbs -> Array containing breadcrumbs + bread_crumbs_component -> if exists show it as headline else show only the breadcrumbs + bread_crumbs_nomenu -> if true will pass false as the value of $menulink + to lonhtmlcommon::breadcrumbs + group -> includes the current group, if page is for a + specific group + use_absolute -> for request for external resource or syllabus, this + will contain https:// if server uses + https (as per hosts.tab), but request is for http + hostname -> hostname, originally from $r->hostname(), (optional). + +=back + +=back + +=cut + +sub start_page { + my ($title,$head_extra,$args) = @_; + #&Apache::lonnet::logthis("start_page ".join(':',caller(0))); + + $env{'internal.start_page'}++; + my ($result,@advtools); + + if (! exists($args->{'skip_phases'}{'head'}) ) { + $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args); + } + + if (! exists($args->{'skip_phases'}{'body'}) ) { + if ($args->{'frameset'}) { + my $attr_string = &make_attr_string($args->{'force_register'}, + $args->{'add_entries'}); + $result .= "\n\n"; + } else { + $result .= + &bodytag($title, + $args->{'function'}, $args->{'add_entries'}, + $args->{'only_body'}, $args->{'domain'}, + $args->{'force_register'}, $args->{'no_nav_bar'}, + $args->{'bgcolor'}, $args, + \@advtools); + } + } + + if ($args->{'js_ready'}) { + $result = &js_ready($result); + } + if ($args->{'html_encode'}) { + $result = &html_encode($result); + } + + # Preparation for new and consistent functionlist at top of screen + # if ($args->{'functionlist'}) { + # $result .= &build_functionlist(); + #} + + # Don't add anything more if only_body wanted or in const space + return $result if $args->{'only_body'} + || $env{'request.state'} eq 'construct'; + + #Breadcrumbs + if (exists($args->{'bread_crumbs'}) or exists($args->{'bread_crumbs_component'})) { + &Apache::lonhtmlcommon::clear_breadcrumbs(); + #if any br links exists, add them to the breadcrumbs + if (exists($args->{'bread_crumbs'}) and ref($args->{'bread_crumbs'}) eq 'ARRAY') { + foreach my $crumb (@{$args->{'bread_crumbs'}}){ + &Apache::lonhtmlcommon::add_breadcrumb($crumb); + } + } + # if @advtools array contains items add then to the breadcrumbs + if (@advtools > 0) { + &Apache::lonmenu::advtools_crumbs(@advtools); + } + my $menulink; + # if arg: bread_crumbs_nomenu is true pass 0 as $menulink item. + if ((exists($args->{'bread_crumbs_nomenu'})) || + ((($args->{'crstype'} eq 'Placement') || (($env{'request.course.id'}) && + ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement'))) && + (!$env{'request.role.adv'}))) { + $menulink = 0; + } else { + undef($menulink); + } + #if bread_crumbs_component exists show it as headline else show only the breadcrumbs + if(exists($args->{'bread_crumbs_component'})){ + $result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'},'',$menulink); + } else { + $result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink); + } + } + return $result; +} + +sub end_page { + my ($args) = @_; + $env{'internal.end_page'}++; + my $result; + if ($args->{'discussion'}) { + my ($target,$parser); + if (ref($args->{'discussion'})) { + ($target,$parser) =($args->{'discussion'}{'target'}, + $args->{'discussion'}{'parser'}); + } + $result .= &Apache::lonxml::xmlend($target,$parser); + } + if ($args->{'frameset'}) { + $result .= ''; + } else { + $result .= &endbodytag($args); + } + unless ($args->{'notbody'}) { + $result .= "\n"; + } + + if ($args->{'js_ready'}) { + $result = &js_ready($result); + } + + if ($args->{'html_encode'}) { + $result = &html_encode($result); + } + + return $result; +} + +sub wishlist_window { + return(<<'ENDWISHLIST'); + +ENDWISHLIST +} + +sub modal_window { + return(<<'ENDMODAL'); + +ENDMODAL +} + +sub modal_link { + my ($link,$linktext,$width,$height,$target,$scrolling,$title,$transparency,$style)=@_; + unless ($width) { $width=480; } + unless ($height) { $height=400; } + unless ($scrolling) { $scrolling='yes'; } + unless ($transparency) { $transparency='true'; } + + my $target_attr; + if (defined($target)) { + $target_attr = 'target="'.$target.'"'; + } + return <<"ENDLINK"; + + $linktext +ENDLINK +} + +sub modal_adhoc_script { + my ($funcname,$width,$height,$content)=@_; + return (< +// + +ENDADHOC +} + +sub modal_adhoc_inner { + my ($funcname,$width,$height,$content)=@_; + my $innerwidth=$width-20; + $content=&js_ready( + &start_page('Dialog',undef,{'only_body'=>1,'bgcolor'=>'#FFFFFF'}). + &start_scrollbox($width.'px',$innerwidth.'px',$height.'px','myModal','#FFFFFF',undef,1). + $content. + &end_scrollbox(). + &end_page() + ); + return &modal_adhoc_script($funcname,$width,$height,$content); +} + +sub modal_adhoc_window { + my ($funcname,$width,$height,$content,$linktext)=@_; + return &modal_adhoc_inner($funcname,$width,$height,$content). + "".$linktext.""; +} + +sub modal_adhoc_launch { + my ($funcname,$width,$height,$content)=@_; + return &modal_adhoc_inner($funcname,$width,$height,$content).(< +// + +ENDLAUNCH +} + +sub modal_adhoc_close { + return (< +// + +ENDCLOSE +} + +sub togglebox_script { + return(< +// + +ENDTOGGLE +} + +sub start_togglebox { + my ($id,$heading,$headerbg,$hidetext,$showtext)=@_; + unless ($heading) { $heading=''; } else { $heading.=' '; } + unless ($showtext) { $showtext=&mt('show'); } + unless ($hidetext) { $hidetext=&mt('hide'); } + unless ($headerbg) { $headerbg='#FFFFFF'; } + return &start_data_table(). + &start_data_table_header_row(). + ''.$heading. + '['.$showtext.']'. + &end_data_table_header_row(). + ''; +} + +sub end_togglebox { + return ''.&end_data_table(); +} + +sub LCprogressbar_script { + my ($id)=@_; + return(< +// + +ENDPROGRESS +} + +sub LCprogressbarUpdate_script { + return(< +.ui-progressbar { position:relative; } +.pblabel { position: absolute; width: 100%; text-align: center; line-height: 1.9em; } + + +ENDPROGRESSUPDATE +} + +my $LClastpercent; +my $LCidcnt; +my $LCcurrentid; + +sub LCprogressbar { + my ($r)=(@_); + $LClastpercent=0; + $LCidcnt++; + $LCcurrentid=$$.'_'.$LCidcnt; + my $starting=&mt('Starting'); + my $content=(< + $starting + +ENDPROGBAR + &r_print($r,$content.&LCprogressbar_script($LCcurrentid)); +} + +sub LCprogressbarUpdate { + my ($r,$val,$text)=@_; + unless ($val) { + if ($LClastpercent) { + $val=$LClastpercent; + } else { + $val=0; + } + } + if ($val<0) { $val=0; } + if ($val>100) { $val=0; } + $LClastpercent=$val; + unless ($text) { $text=$val.'%'; } + $text=&js_ready($text); + &r_print($r,< +// + +ENDUPDATE +} + +sub LCprogressbarClose { + my ($r)=@_; + $LClastpercent=0; + &r_print($r,< +// + +ENDCLOSE +} + +sub r_print { + my ($r,$to_print)=@_; + if ($r) { + $r->print($to_print); + $r->rflush(); + } else { + print($to_print); + } +} + +sub html_encode { + my ($result) = @_; + + $result = &HTML::Entities::encode($result,'<>&"'); + + return $result; +} + +sub js_ready { + my ($result) = @_; + + $result =~ s/[\n\r]/ /xmsg; + $result =~ s/\\/\\\\/xmsg; + $result =~ s/'/\\'/xmsg; + $result =~ s{ 1) { + &Apache::lonnet::logthis('start_page called multiple times '. + $env{'internal.start_page'}.' '. + $ENV{'request.filename'}); + } + if ( exists($env{'internal.end_page'}) + && $env{'internal.end_page'} > 1) { + &Apache::lonnet::logthis('end_page called multiple times '. + $env{'internal.end_page'}.' '. + $env{'request.filename'}); + } + if ( exists($env{'internal.start_page'}) + && ! exists($env{'internal.end_page'})) { + &Apache::lonnet::logthis('start_page called without end_page '. + $env{'request.filename'}); + } + if ( ! exists($env{'internal.start_page'}) + && exists($env{'internal.end_page'})) { + &Apache::lonnet::logthis('end_page called without start_page'. + $env{'request.filename'}); + } +} + + +sub start_scrollbox { + my ($outerwidth,$width,$height,$id,$bgcolor,$cursor,$needjsready) = @_; + unless ($outerwidth) { $outerwidth='520px'; } + unless ($width) { $width='500px'; } + unless ($height) { $height='200px'; } + my ($table_id,$div_id,$tdcol); + if ($id ne '') { + $table_id = ' id="table_'.$id.'"'; + $div_id = ' id="div_'.$id.'"'; + } + if ($bgcolor ne '') { + $tdcol = "background-color: $bgcolor;"; + } + my $nicescroll_js; + if ($env{'browser.mobile'}) { + $nicescroll_js = &nicescroll_javascript('div_'.$id,$cursor,$needjsready); + } + return <<"END"; +$nicescroll_js + +
+
+END +} + +sub end_scrollbox { + return '
'; +} + +sub nicescroll_javascript { + my ($id,$cursor,$needjsready,$framecheck,$location) = @_; + my %options; + if (ref($cursor) eq 'HASH') { + %options = %{$cursor}; + } + unless ($options{'railalign'} =~ /^left|right$/) { + $options{'railalign'} = 'left'; + } + unless ($options{'cursorcolor'} =~ /^\#\w+$/) { + my $function = &get_users_function(); + $options{'cursorcolor'} = &designparm($function.'.sidebg',$env{'request.role.domain'}); + unless ($options{'cursorcolor'} =~ /^\#\w+$/) { + $options{'cursorcolor'} = '#00F'; + } + } + if ($options{'cursoropacity'} =~ /^[\d.]+$/) { + unless ($options{'cursoropacity'} >= 0.0 && $options{'cursoropacity'} <=1.0) { + $options{'cursoropacity'}='1.0'; + } + } else { + $options{'cursoropacity'}='1.0'; + } + if ($options{'cursorfixedheight'} eq 'none') { + delete($options{'cursorfixedheight'}); + } else { + unless ($options{'cursorfixedheight'} =~ /^\d+$/) { $options{'cursorfixedheight'}='50'; } + } + unless ($options{'railoffset'} =~ /^{[\w\:\d\-,]+}$/) { + delete($options{'railoffset'}); + } + my @niceoptions; + while (my($key,$value) = each(%options)) { + if ($value =~ /^\{.+\}$/) { + push(@niceoptions,$key.':'.$value); + } else { + push(@niceoptions,$key.':"'.$value.'"'); + } + } + my $nicescroll_js = ' +$(document).ready( + function() { + $("#'.$id.'").niceScroll({'.join(',',@niceoptions).'}); + } +); +'; + if ($framecheck) { + $nicescroll_js .= ' +function expand_div(caller) { + if (top === self) { + document.getElementById("'.$id.'").style.width = "auto"; + document.getElementById("'.$id.'").style.height = "auto"; + } else { + try { + if (parent.frames) { + if (parent.frames.length > 1) { + var framesrc = parent.frames[1].location.href; + var currsrc = framesrc.replace(/\#.*$/,""); + if ((caller == "search") || (currsrc == "'.$location.'")) { + document.getElementById("'.$id.'").style.width = "auto"; + document.getElementById("'.$id.'").style.height = "auto"; + } + } + } + } catch (e) { + return; + } + } + return; +} +'; + } + if ($needjsready) { + $nicescroll_js = ' +\n"; + } else { + $nicescroll_js = &Apache::lonhtmlcommon::scripttag($nicescroll_js); + } + return $nicescroll_js; +} + +sub simple_error_page { + my ($r,$title,$msg,$args) = @_; + if (ref($args) eq 'HASH') { + if (!$args->{'no_auto_mt_msg'}) { $msg = &mt($msg); } + } else { + $msg = &mt($msg); + } + + my $page = + &Apache::loncommon::start_page($title). + '

'.$msg.'

'. + &Apache::loncommon::end_page(); + if (ref($r)) { + $r->print($page); + return; + } + return $page; +} + +{ + my @row_count; + + sub start_data_table_count { + unshift(@row_count, 0); + return; + } + + sub end_data_table_count { + shift(@row_count); + return; + } + + sub start_data_table { + my ($add_class,$id) = @_; + my $css_class = (join(' ','LC_data_table',$add_class)); + my $table_id; + if (defined($id)) { + $table_id = ' id="'.$id.'"'; + } + &start_data_table_count(); + return ''."\n"; + } + + sub end_data_table { + &end_data_table_count(); + return '
'."\n";; + } + + sub start_data_table_row { + my ($add_class, $id) = @_; + $row_count[0]++; + my $css_class = ($row_count[0] % 2)?'LC_odd_row':'LC_even_row'; + $css_class = (join(' ',$css_class,$add_class)) unless ($add_class eq ''); + $id = (' id="'.$id.'"') unless ($id eq ''); + return ''."\n"; + } + + sub continue_data_table_row { + my ($add_class, $id) = @_; + my $css_class = ($row_count[0] % 2)?'LC_odd_row':'LC_even_row'; + $css_class = (join(' ',$css_class,$add_class)) unless ($add_class eq ''); + $id = (' id="'.$id.'"') unless ($id eq ''); + return ''."\n"; + } + + sub end_data_table_row { + return ''."\n";; + } + + sub start_data_table_empty_row { +# $row_count[0]++; + return ''."\n";; + } + + sub end_data_table_empty_row { + return ''."\n";; + } + + sub start_data_table_header_row { + return ''."\n";; + } + + sub end_data_table_header_row { + return ''."\n";; + } + + sub data_table_caption { + my $caption = shift; + return "$caption"; + } +} + +=pod + +=item * &inhibit_menu_check($arg) + +Checks for a inhibitmenu state and generates output to preserve it + +Inputs: $arg - can be any of + - undef - in which case the return value is a string + to add into arguments list of a uri + - 'input' - in which case the return value is a HTML +
field of type hidden to + preserve the value + - a url - in which case the return value is the url with + the neccesary cgi args added to preserve the + inhibitmenu state + - a ref to a url - no return value, but the string is + updated to include the neccessary cgi + args to preserve the inhibitmenu state + +=cut + +sub inhibit_menu_check { + my ($arg) = @_; + &get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['inhibitmenu']); + if ($arg eq 'input') { + if ($env{'form.inhibitmenu'}) { + return ''; + } else { + return + } + } + if ($env{'form.inhibitmenu'}) { + if (ref($arg)) { + $$arg .= '?inhibitmenu='.$env{'form.inhibitmenu'}; + } elsif ($arg eq '') { + $arg .= 'inhibitmenu='.$env{'form.inhibitmenu'}; + } else { + $arg .= '?inhibitmenu='.$env{'form.inhibitmenu'}; + } + } + if (!ref($arg)) { + return $arg; + } +} + +############################################### + +=pod + +=back + +=head1 User Information Routines + +=over 4 + +=item * &get_users_function() + +Used by &bodytag to determine the current users primary role. +Returns either 'student','coordinator','admin', or 'author'. + +=cut + +############################################### +sub get_users_function { + my $function = 'norole'; + if ($env{'request.role'}=~/^(st)/) { + $function='student'; + } + if ($env{'request.role'}=~/^(cc|co|in|ta|ep)/) { + $function='coordinator'; + } + if ($env{'request.role'}=~/^(su|dc|ad|li)/) { + $function='admin'; + } + if (($env{'request.role'}=~/^(au|ca|aa)/) || + ($ENV{'REQUEST_URI'}=~ m{/^(/priv)})) { + $function='author'; + } + return $function; +} + +############################################### + +=pod + +=item * &show_course() + +Used by lonmenu.pm and lonroles.pm to determine whether to use the word +'Courses' or 'Roles' in inline navigation and on screen displaying user's roles. + +Inputs: +None + +Outputs: +Scalar: 1 if 'Course' to be used, 0 otherwise. + +=cut + +############################################### +sub show_course { + my $course = !$env{'user.adv'}; + if (!$env{'user.adv'}) { + foreach my $env (keys(%env)) { + next if ($env !~ m/^user\.priv\./); + if ($env !~ m/^user\.priv\.(?:st|cm)/) { + $course = 0; + last; + } + } + } + return $course; +} + +############################################### + +=pod + +=item * &check_user_status() + +Determines current status of supplied role for a +specific user. Roles can be active, previous or future. + +Inputs: +user's domain, user's username, course's domain, +course's number, optional section ID. + +Outputs: +role status: active, previous or future. + +=cut + +sub check_user_status { + my ($udom,$uname,$cdom,$crs,$role,$sec) = @_; + my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname); + my @uroles = keys(%userinfo); + my $srchstr; + my $active_chk = 'none'; + my $now = time; + if (@uroles > 0) { + if (($role eq 'cc') || ($role eq 'co') || ($sec eq '') || (!defined($sec))) { + $srchstr = '/'.$cdom.'/'.$crs.'_'.$role; + } else { + $srchstr = '/'.$cdom.'/'.$crs.'/'.$sec.'_'.$role; + } + if (grep/^\Q$srchstr\E$/,@uroles) { + my $role_end = 0; + my $role_start = 0; + $active_chk = 'active'; + if ($userinfo{$srchstr} =~ m/^\Q$role\E_(\d+)/) { + $role_end = $1; + if ($userinfo{$srchstr} =~ m/^\Q$role\E_\Q$role_end\E_(\d+)$/) { + $role_start = $1; + } + } + if ($role_start > 0) { + if ($now < $role_start) { + $active_chk = 'future'; + } + } + if ($role_end > 0) { + if ($now > $role_end) { + $active_chk = 'previous'; + } + } + } + } + return $active_chk; +} + +############################################### + +=pod + +=item * &get_sections() + +Determines all the sections for a course including +sections with students and sections containing other roles. +Incoming parameters: + +1. domain +2. course number +3. reference to array containing roles for which sections should +be gathered (optional). +4. reference to array containing status types for which sections +should be gathered (optional). + +If the third argument is undefined, sections are gathered for any role. +If the fourth argument is undefined, sections are gathered for any status. +Permissible values are 'active' or 'future' or 'previous'. + +Returns section hash (keys are section IDs, values are +number of users in each section), subject to the +optional roles filter, optional status filter + +=cut + +############################################### +sub get_sections { + my ($cdom,$cnum,$possible_roles,$possible_status) = @_; + if (!defined($cdom) || !defined($cnum)) { + my $cid = $env{'request.course.id'}; + + return if (!defined($cid)); + + $cdom = $env{'course.'.$cid.'.domain'}; + $cnum = $env{'course.'.$cid.'.num'}; + } + + my %sectioncount; + my $now = time; + + my $check_students = 1; + my $only_students = 0; + if (ref($possible_roles) eq 'ARRAY') { + if (grep(/^st$/,@{$possible_roles})) { + if (@{$possible_roles} == 1) { + $only_students = 1; + } + } else { + $check_students = 0; + } + } + + if ($check_students) { + my ($classlist) = &Apache::loncoursedata::get_classlist($cdom,$cnum); + my $sec_index = &Apache::loncoursedata::CL_SECTION(); + my $status_index = &Apache::loncoursedata::CL_STATUS(); + my $start_index = &Apache::loncoursedata::CL_START(); + my $end_index = &Apache::loncoursedata::CL_END(); + my $status; + while (my ($student,$data) = each(%$classlist)) { + my ($section,$stu_status,$start,$end) = ($data->[$sec_index], + $data->[$status_index], + $data->[$start_index], + $data->[$end_index]); + if ($stu_status eq 'Active') { + $status = 'active'; + } elsif ($end < $now) { + $status = 'previous'; + } elsif ($start > $now) { + $status = 'future'; + } + if ($section ne '-1' && $section !~ /^\s*$/) { + if ((!defined($possible_status)) || (($status ne '') && + (grep/^\Q$status\E$/,@{$possible_status}))) { + $sectioncount{$section}++; + } + } + } + } + if ($only_students) { + return %sectioncount; + } + my %courseroles = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum); + foreach my $user (sort(keys(%courseroles))) { + if ($user !~ /^(\w{2})/) { next; } + my ($role) = ($user =~ /^(\w{2})/); + if ($possible_roles && !(grep(/^$role$/,@$possible_roles))) { next; } + my ($section,$status); + if ($role eq 'cr' && + $user =~ m-^$role/[^/]*/[^/]*/[^/]*:[^:]*:[^:]*:(\w+)-) { + $section=$1; + } + if ($user =~ /^$role:[^:]*:[^:]*:(\w+)/) { $section=$1; } + if (!defined($section) || $section eq '-1') { next; } + my ($end,$start) = ($courseroles{$user} =~ /^([^:]*):([^:]*)$/); + if ($end == -1 && $start == -1) { + next; #deleted role + } + if (!defined($possible_status)) { + $sectioncount{$section}++; + } else { + if ((!$end || $end >= $now) && (!$start || $start <= $now)) { + $status = 'active'; + } elsif ($end < $now) { + $status = 'future'; + } elsif ($start > $now) { + $status = 'previous'; + } + if (($status ne '') && (grep/^\Q$status\E$/,@{$possible_status})) { + $sectioncount{$section}++; + } + } + } + return %sectioncount; +} + +############################################### + +=pod + +=item * &get_course_users() + +Retrieves usernames:domains for users in the specified course +with specific role(s), and access status. + +Incoming parameters: +1. course domain +2. course number +3. access status: users must have - either active, +previous, future, or all. +4. reference to array of permissible roles +5. reference to array of section restrictions (optional) +6. reference to results object (hash of hashes). +7. reference to optional userdata hash +8. reference to optional statushash +9. flag if privileged users (except those set to unhide in + course settings) should be excluded +Keys of top level results hash are roles. +Keys of inner hashes are username:domain, with +values set to access type. +Optional userdata hash returns an array with arguments in the +same order as loncoursedata::get_classlist() for student data. + +Optional statushash returns + +Entries for end, start, section and status are blank because +of the possibility of multiple values for non-student roles. + +=cut + +############################################### + +sub get_course_users { + my ($cdom,$cnum,$types,$roles,$sections,$users,$userdata,$statushash,$hidepriv) = @_; + my %idx = (); + my %seclists; + + $idx{udom} = &Apache::loncoursedata::CL_SDOM(); + $idx{uname} = &Apache::loncoursedata::CL_SNAME(); + $idx{end} = &Apache::loncoursedata::CL_END(); + $idx{start} = &Apache::loncoursedata::CL_START(); + $idx{id} = &Apache::loncoursedata::CL_ID(); + $idx{section} = &Apache::loncoursedata::CL_SECTION(); + $idx{fullname} = &Apache::loncoursedata::CL_FULLNAME(); + $idx{status} = &Apache::loncoursedata::CL_STATUS(); + + if (grep(/^st$/,@{$roles})) { + my ($classlist,$keylist)=&Apache::loncoursedata::get_classlist($cdom,$cnum); + my $now = time; + foreach my $student (keys(%{$classlist})) { + my $match = 0; + my $secmatch = 0; + my $section = $$classlist{$student}[$idx{section}]; + my $status = $$classlist{$student}[$idx{status}]; + if ($section eq '') { + $section = 'none'; + } + if ((ref($sections) eq 'ARRAY') && (@{$sections} > 0)) { + if (grep(/^all$/,@{$sections})) { + $secmatch = 1; + } elsif ($$classlist{$student}[$idx{section}] eq '') { + if (grep(/^none$/,@{$sections})) { + $secmatch = 1; + } + } else { + if (grep(/^\Q$section\E$/,@{$sections})) { + $secmatch = 1; + } + } + if (!$secmatch) { + next; + } + } + if (defined($$types{'active'})) { + if ($$classlist{$student}[$idx{status}] eq 'Active') { + push(@{$$users{st}{$student}},'active'); + $match = 1; + } + } + if (defined($$types{'previous'})) { + if ($$classlist{$student}[$idx{status}] eq 'Expired') { + push(@{$$users{st}{$student}},'previous'); + $match = 1; + } + } + if (defined($$types{'future'})) { + if ($$classlist{$student}[$idx{status}] eq 'Future') { + push(@{$$users{st}{$student}},'future'); + $match = 1; + } + } + if ($match) { + push(@{$seclists{$student}},$section); + if (ref($userdata) eq 'HASH') { + $$userdata{$student} = $$classlist{$student}; + } + if (ref($statushash) eq 'HASH') { + $statushash->{$student}{'st'}{$section} = $status; + } + } + } + } + if ((@{$roles} > 1) || ((@{$roles} == 1) && ($$roles[0] ne "st"))) { + my %coursepersonnel = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum); + my $now = time; + my %displaystatus = ( previous => 'Expired', + active => 'Active', + future => 'Future', + ); + my (%nothide,@possdoms); + if ($hidepriv) { + my %coursehash=&Apache::lonnet::coursedescription($cdom.'_'.$cnum); + foreach my $user (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) { + if ($user !~ /:/) { + $nothide{join(':',split(/[\@]/,$user))}=1; + } else { + $nothide{$user} = 1; + } + } + my @possdoms = ($cdom); + if ($coursehash{'checkforpriv'}) { + push(@possdoms,split(/,/,$coursehash{'checkforpriv'})); + } + } + foreach my $person (sort(keys(%coursepersonnel))) { + my $match = 0; + my $secmatch = 0; + my $status; + my ($role,$user,$usec) = ($person =~ /^([^:]*):([^:]+:[^:]+):([^:]*)/); + $user =~ s/:$//; + my ($end,$start) = split(/:/,$coursepersonnel{$person}); + if ($end == -1 || $start == -1) { + next; + } + if (($role) && ((grep(/^\Q$role\E$/,@{$roles})) || + (grep(/^cr$/,@{$roles}) && $role =~ /^cr\//))) { + my ($uname,$udom) = split(/:/,$user); + if ((ref($sections) eq 'ARRAY') && (@{$sections} > 0)) { + if (grep(/^all$/,@{$sections})) { + $secmatch = 1; + } elsif ($usec eq '') { + if (grep(/^none$/,@{$sections})) { + $secmatch = 1; + } + } else { + if (grep(/^\Q$usec\E$/,@{$sections})) { + $secmatch = 1; + } + } + if (!$secmatch) { + next; + } + } + if ($usec eq '') { + $usec = 'none'; + } + if ($uname ne '' && $udom ne '') { + if ($hidepriv) { + if ((&Apache::lonnet::privileged($uname,$udom,\@possdoms)) && + (!$nothide{$uname.':'.$udom})) { + next; + } + } + if ($end > 0 && $end < $now) { + $status = 'previous'; + } elsif ($start > $now) { + $status = 'future'; + } else { + $status = 'active'; + } + foreach my $type (keys(%{$types})) { + if ($status eq $type) { + if (!grep(/^\Q$type\E$/,@{$$users{$role}{$user}})) { + push(@{$$users{$role}{$user}},$type); + } + $match = 1; + } + } + if (($match) && (ref($userdata) eq 'HASH')) { + if (!exists($$userdata{$uname.':'.$udom})) { + &get_user_info($udom,$uname,\%idx,$userdata); + } + if (!grep(/^\Q$usec\E$/,@{$seclists{$uname.':'.$udom}})) { + push(@{$seclists{$uname.':'.$udom}},$usec); + } + if (ref($statushash) eq 'HASH') { + $statushash->{$uname.':'.$udom}{$role}{$usec} = $displaystatus{$status}; + } + } + } + } + } + if (grep(/^ow$/,@{$roles})) { + if ((defined($cdom)) && (defined($cnum))) { + my %csettings = &Apache::lonnet::get('environment',['internal.courseowner'],$cdom,$cnum); + if ( defined($csettings{'internal.courseowner'}) ) { + my $owner = $csettings{'internal.courseowner'}; + next if ($owner eq ''); + my ($ownername,$ownerdom); + if ($owner =~ /^([^:]+):([^:]+)$/) { + $ownername = $1; + $ownerdom = $2; + } else { + $ownername = $owner; + $ownerdom = $cdom; + $owner = $ownername.':'.$ownerdom; + } + @{$$users{'ow'}{$owner}} = 'any'; + if (defined($userdata) && + !exists($$userdata{$owner})) { + &get_user_info($ownerdom,$ownername,\%idx,$userdata); + if (!grep(/^none$/,@{$seclists{$owner}})) { + push(@{$seclists{$owner}},'none'); + } + if (ref($statushash) eq 'HASH') { + $statushash->{$owner}{'ow'}{'none'} = 'Any'; + } + } + } + } + } + foreach my $user (keys(%seclists)) { + @{$seclists{$user}} = (sort {$a <=> $b} @{$seclists{$user}}); + $$userdata{$user}[$idx{section}] = join(',',@{$seclists{$user}}); + } + } + return; +} + +sub get_user_info { + my ($udom,$uname,$idx,$userdata) = @_; + $$userdata{$uname.':'.$udom}[$$idx{fullname}] = + &plainname($uname,$udom,'lastname'); + $$userdata{$uname.':'.$udom}[$$idx{uname}] = $uname; + $$userdata{$uname.':'.$udom}[$$idx{udom}] = $udom; + my %idhash = &Apache::lonnet::idrget($udom,($uname)); + $$userdata{$uname.':'.$udom}[$$idx{id}] = $idhash{$uname}; + return; +} + +############################################### + +=pod + +=item * &get_user_quota() + +Retrieves quota assigned for storage of user files. +Default is to report quota for portfolio files. + +Incoming parameters: +1. user's username +2. user's domain +3. quota name - portfolio, author, or course + (if no quota name provided, defaults to portfolio). +4. crstype - official, unofficial, textbook, placement or community, + if quota name is course + +Returns: +1. Disk quota (in MB) assigned to student. +2. (Optional) Type of setting: custom or default + (individually assigned or default for user's + institutional status). +3. (Optional) - User's institutional status (e.g., faculty, staff + or student - types as defined in localenroll::inst_usertypes + for user's domain, which determines default quota for user. +4. (Optional) - Default quota which would apply to the user. + +If a value has been stored in the user's environment, +it will return that, otherwise it returns the maximal default +defined for the user's institutional status(es) in the domain. + +=cut + +############################################### + + +sub get_user_quota { + my ($uname,$udom,$quotaname,$crstype) = @_; + my ($quota,$quotatype,$settingstatus,$defquota); + if (!defined($udom)) { + $udom = $env{'user.domain'}; + } + if (!defined($uname)) { + $uname = $env{'user.name'}; + } + if (($udom eq '' || $uname eq '') || + ($udom eq 'public') && ($uname eq 'public')) { + $quota = 0; + $quotatype = 'default'; + $defquota = 0; + } else { + my $inststatus; + if ($quotaname eq 'course') { + if (($env{'course.'.$udom.'_'.$uname.'.num'} eq $uname) && + ($env{'course.'.$udom.'_'.$uname.'.domain'} eq $udom)) { + $quota = $env{'course.'.$udom.'_'.$uname.'.internal.uploadquota'}; + } else { + my %cenv = &Apache::lonnet::coursedescription("$udom/$uname"); + $quota = $cenv{'internal.uploadquota'}; + } + } else { + if ($udom eq $env{'user.domain'} && $uname eq $env{'user.name'}) { + if ($quotaname eq 'author') { + $quota = $env{'environment.authorquota'}; + } else { + $quota = $env{'environment.portfolioquota'}; + } + $inststatus = $env{'environment.inststatus'}; + } else { + my %userenv = + &Apache::lonnet::get('environment',['portfolioquota', + 'authorquota','inststatus'],$udom,$uname); + my ($tmp) = keys(%userenv); + if ($tmp !~ /^(con_lost|error|no_such_host)/i) { + if ($quotaname eq 'author') { + $quota = $userenv{'authorquota'}; + } else { + $quota = $userenv{'portfolioquota'}; + } + $inststatus = $userenv{'inststatus'}; + } else { + undef(%userenv); + } + } + } + if ($quota eq '' || wantarray) { + if ($quotaname eq 'course') { + my %domdefs = &Apache::lonnet::get_domain_defaults($udom); + if (($crstype eq 'official') || ($crstype eq 'unofficial') || + ($crstype eq 'community') || ($crstype eq 'textbook') || + ($crstype eq 'placement')) { + $defquota = $domdefs{$crstype.'quota'}; + } + if ($defquota eq '') { + $defquota = 500; + } + } else { + ($defquota,$settingstatus) = &default_quota($udom,$inststatus,$quotaname); + } + if ($quota eq '') { + $quota = $defquota; + $quotatype = 'default'; + } else { + $quotatype = 'custom'; + } + } + } + if (wantarray) { + return ($quota,$quotatype,$settingstatus,$defquota); + } else { + return $quota; + } +} + +############################################### + +=pod + +=item * &default_quota() + +Retrieves default quota assigned for storage of user portfolio files, +given an (optional) user's institutional status. + +Incoming parameters: + +1. domain +2. (Optional) institutional status(es). This is a : separated list of + status types (e.g., faculty, staff, student etc.) + which apply to the user for whom the default is being retrieved. + If the institutional status string in undefined, the domain + default quota will be returned. +3. quota name - portfolio, author, or course + (if no quota name provided, defaults to portfolio). + +Returns: + +1. Default disk quota (in MB) for user portfolios in the domain. +2. (Optional) institutional type which determined the value of the + default quota. + +If a value has been stored in the domain's configuration db, +it will return that, otherwise it returns 20 (for backwards +compatibility with domains which have not set up a configuration +db file; the original statically defined portfolio quota was 20 MB). + +If the user's status includes multiple types (e.g., staff and student), +the largest default quota which applies to the user determines the +default quota returned. + +=cut + +############################################### + + +sub default_quota { + my ($udom,$inststatus,$quotaname) = @_; + my ($defquota,$settingstatus); + my %quotahash = &Apache::lonnet::get_dom('configuration', + ['quotas'],$udom); + my $key = 'defaultquota'; + if ($quotaname eq 'author') { + $key = 'authorquota'; + } + if (ref($quotahash{'quotas'}) eq 'HASH') { + if ($inststatus ne '') { + my @statuses = map { &unescape($_); } split(/:/,$inststatus); + foreach my $item (@statuses) { + if (ref($quotahash{'quotas'}{$key}) eq 'HASH') { + if ($quotahash{'quotas'}{$key}{$item} ne '') { + if ($defquota eq '') { + $defquota = $quotahash{'quotas'}{$key}{$item}; + $settingstatus = $item; + } elsif ($quotahash{'quotas'}{$key}{$item} > $defquota) { + $defquota = $quotahash{'quotas'}{$key}{$item}; + $settingstatus = $item; + } + } + } elsif ($key eq 'defaultquota') { + if ($quotahash{'quotas'}{$item} ne '') { + if ($defquota eq '') { + $defquota = $quotahash{'quotas'}{$item}; + $settingstatus = $item; + } elsif ($quotahash{'quotas'}{$item} > $defquota) { + $defquota = $quotahash{'quotas'}{$item}; + $settingstatus = $item; + } + } + } + } + } + if ($defquota eq '') { + if (ref($quotahash{'quotas'}{$key}) eq 'HASH') { + $defquota = $quotahash{'quotas'}{$key}{'default'}; + } elsif ($key eq 'defaultquota') { + $defquota = $quotahash{'quotas'}{'default'}; + } + $settingstatus = 'default'; + if ($defquota eq '') { + if ($quotaname eq 'author') { + $defquota = 500; + } + } + } + } else { + $settingstatus = 'default'; + if ($quotaname eq 'author') { + $defquota = 500; + } else { + $defquota = 20; + } + } + if (wantarray) { + return ($defquota,$settingstatus); + } else { + return $defquota; + } +} + +############################################### + +=pod + +=item * &excess_filesize_warning() + +Returns warning message if upload of file to authoring space, or copying +of existing file within authoring space will cause quota for the authoring +space to be exceeded. + +Same, if upload of a file directly to a course/community via Course Editor +will cause quota for uploaded content for the course to be exceeded. + +Inputs: 7 +1. username or coursenum +2. domain +3. context ('author' or 'course') +4. filename of file for which action is being requested +5. filesize (kB) of file +6. action being taken: copy or upload. +7. quotatype (in course context -- official, unofficial, textbook, placement or community). + +Returns: 1 scalar: HTML to display containing warning if quota would be exceeded, + otherwise return null. + +=back + +=cut + +sub excess_filesize_warning { + my ($uname,$udom,$context,$filename,$filesize,$action,$quotatype) = @_; + my $current_disk_usage = 0; + my $disk_quota = &get_user_quota($uname,$udom,$context,$quotatype); #expressed in MB + if ($context eq 'author') { + my $authorspace = $Apache::lonnet::perlvar{'lonDocRoot'}."/priv/$udom/$uname"; + $current_disk_usage = &Apache::lonnet::diskusage($udom,$uname,$authorspace); + } else { + foreach my $subdir ('docs','supplemental') { + $current_disk_usage += &Apache::lonnet::diskusage($udom,$uname,"userfiles/$subdir",1); + } + } + $disk_quota = int($disk_quota * 1000); + if (($current_disk_usage + $filesize) > $disk_quota) { + return '

'. + &mt("Unable to $action [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.", + ''.$filename.'',$filesize).'

'. + '

'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.', + $disk_quota,$current_disk_usage). + '

'; + } + return; +} + +############################################### + + + + +sub get_secgrprole_info { + my ($cdom,$cnum,$needroles,$type) = @_; + my %sections_count = &get_sections($cdom,$cnum); + my @sections = (sort {$a <=> $b} keys(%sections_count)); + my %curr_groups = &Apache::longroup::coursegroups($cdom,$cnum); + my @groups = sort(keys(%curr_groups)); + my $allroles = []; + my $rolehash; + my $accesshash = { + active => 'Currently has access', + future => 'Will have future access', + previous => 'Previously had access', + }; + if ($needroles) { + $rolehash = {'all' => 'all'}; + my %user_roles = &Apache::lonnet::dump('nohist_userroles',$cdom,$cnum); + if (&Apache::lonnet::error(%user_roles)) { + undef(%user_roles); + } + foreach my $item (keys(%user_roles)) { + my ($role)=split(/\:/,$item,2); + if ($role eq 'cr') { next; } + if ($role =~ /^cr/) { + $$rolehash{$role} = (split('/',$role))[3]; + } else { + $$rolehash{$role} = &Apache::lonnet::plaintext($role,$type); + } + } + foreach my $key (sort(keys(%{$rolehash}))) { + push(@{$allroles},$key); + } + push (@{$allroles},'st'); + $$rolehash{'st'} = &Apache::lonnet::plaintext('st',$type); + } + return (\@sections,\@groups,$allroles,$rolehash,$accesshash); +} + +sub user_picker { + my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context,$fixeddom,$noinstd) = @_; + my $currdom = $dom; + my @alldoms = &Apache::lonnet::all_domains(); + if (@alldoms == 1) { + my %domsrch = &Apache::lonnet::get_dom('configuration', + ['directorysrch'],$alldoms[0]); + my $domdesc = &Apache::lonnet::domain($alldoms[0],'description'); + my $showdom = $domdesc; + if ($showdom eq '') { + $showdom = $dom; + } + if (ref($domsrch{'directorysrch'}) eq 'HASH') { + if ((!$domsrch{'directorysrch'}{'available'}) && + ($domsrch{'directorysrch'}{'lcavailable'} eq '0')) { + return (&mt('LON-CAPA directory search is not available in domain: [_1]',$showdom),0); + } + } + } + my %curr_selected = ( + srchin => 'dom', + srchby => 'lastname', + ); + my $srchterm; + if ((ref($srch) eq 'HASH') && ($env{'form.origform'} ne 'crtusername')) { + if ($srch->{'srchby'} ne '') { + $curr_selected{'srchby'} = $srch->{'srchby'}; + } + if ($srch->{'srchin'} ne '') { + $curr_selected{'srchin'} = $srch->{'srchin'}; + } + if ($srch->{'srchtype'} ne '') { + $curr_selected{'srchtype'} = $srch->{'srchtype'}; + } + if ($srch->{'srchdomain'} ne '') { + $currdom = $srch->{'srchdomain'}; + } + $srchterm = $srch->{'srchterm'}; + } + my %html_lt=&Apache::lonlocal::texthash( + 'usr' => 'Search criteria', + 'doma' => 'Domain/institution to search', + 'uname' => 'username', + 'lastname' => 'last name', + 'lastfirst' => 'last name, first name', + 'crs' => 'in this course', + 'dom' => 'in selected LON-CAPA domain', + 'alc' => 'all LON-CAPA', + 'instd' => 'in institutional directory for selected domain', + 'exact' => 'is', + 'contains' => 'contains', + 'begins' => 'begins with', + ); + my %js_lt=&Apache::lonlocal::texthash( + 'youm' => "You must include some text to search for.", + 'thte' => "The text you are searching for must contain at least two characters when using a 'begins' type search.", + 'thet' => "The text you are searching for must contain at least three characters when using a 'contains' type search.", + 'yomc' => "You must choose a domain when using an institutional directory search.", + 'ymcd' => "You must choose a domain when using a domain search.", + 'whus' => "When using searching by last,first you must include a comma as separator between last name and first name.", + 'whse' => "When searching by last,first you must include at least one character in the first name.", + 'thfo' => "The following need to be corrected before the search can be run:", + ); + &html_escape(\%html_lt); + &js_escape(\%js_lt); + my $domform; + my $allow_blank = 1; + if ($fixeddom) { + $allow_blank = 0; + $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1,undef,[$currdom]); + } else { + $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1); + } + my $srchinsel = ' \n"; + + my $srchbysel = ' \n"; + + my $srchtypesel = ' \n"; + + my ($newuserscript,$new_user_create); + my $context_dom = $env{'request.role.domain'}; + if ($context eq 'requestcrs') { + if ($env{'form.coursedom'} ne '') { + $context_dom = $env{'form.coursedom'}; + } + } + if ($forcenewuser) { + if (ref($srch) eq 'HASH') { + if ($srch->{'srchby'} eq 'uname' && $srch->{'srchtype'} eq 'exact' && $srch->{'srchin'} eq 'dom' && $srch->{'srchdomain'} eq $context_dom) { + if ($cancreate) { + $new_user_create = '

&"').'" onclick="javascript:setSearch(\'1\','.$caller.');" />

'; + } else { + my $helplink = 'javascript:helpMenu('."'display'".')'; + my %usertypetext = ( + official => 'institutional', + unofficial => 'non-institutional', + ); + $new_user_create = '

' + .&mt("You are not authorized to create new $usertypetext{$usertype} users in this domain.") + .' ' + .&mt('Please contact the [_1]helpdesk[_2] for assistance.' + ,'','') + .'


'; + } + } + } + + $newuserscript = <<"ENDSCRIPT"; + +function setSearch(createnew,callingForm) { + if (createnew == 1) { + for (var i=0; i +// + + +$new_user_create + +END_BLOCK + + $output .= &Apache::lonhtmlcommon::start_pick_box(). + &Apache::lonhtmlcommon::row_title($html_lt{'doma'}). + $domform. + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title($html_lt{'usr'}). + $srchbysel. + $srchtypesel. + ''. + $srchinsel. + &Apache::lonhtmlcommon::row_closure(1). + &Apache::lonhtmlcommon::end_pick_box(). + '
'; + return ($output,1); +} + +sub user_rule_check { + my ($usershash,$checks,$alerts,$rulematch,$inst_results,$curr_rules,$got_rules) = @_; + my ($response,%inst_response); + if (ref($usershash) eq 'HASH') { + if (keys(%{$usershash}) > 1) { + my (%by_username,%by_id,%userdoms); + my $checkid; + if (ref($checks) eq 'HASH') { + if ((!defined($checks->{'username'})) && (defined($checks->{'id'}))) { + $checkid = 1; + } + } + foreach my $user (keys(%{$usershash})) { + my ($uname,$udom) = split(/:/,$user); + if ($checkid) { + if (ref($usershash->{$user}) eq 'HASH') { + if ($usershash->{$user}->{'id'} ne '') { + $by_id{$udom}{$usershash->{$user}->{'id'}} = $uname; + $userdoms{$udom} = 1; + if (ref($inst_results) eq 'HASH') { + $inst_results->{$uname.':'.$udom} = {}; + } + } + } + } else { + $by_username{$udom}{$uname} = 1; + $userdoms{$udom} = 1; + if (ref($inst_results) eq 'HASH') { + $inst_results->{$uname.':'.$udom} = {}; + } + } + } + foreach my $udom (keys(%userdoms)) { + if (!$got_rules->{$udom}) { + my %domconfig = &Apache::lonnet::get_dom('configuration', + ['usercreation'],$udom); + if (ref($domconfig{'usercreation'}) eq 'HASH') { + foreach my $item ('username','id') { + if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') { + $$curr_rules{$udom}{$item} = + $domconfig{'usercreation'}{$item.'_rule'}; + } + } + } + $got_rules->{$udom} = 1; + } + } + if ($checkid) { + foreach my $udom (keys(%by_id)) { + my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_id{$udom},'id'); + if ($outcome eq 'ok') { + foreach my $id (keys(%{$by_id{$udom}})) { + my $uname = $by_id{$udom}{$id}; + $inst_response{$uname.':'.$udom} = $outcome; + } + if (ref($results) eq 'HASH') { + foreach my $uname (keys(%{$results})) { + if (exists($inst_response{$uname.':'.$udom})) { + $inst_response{$uname.':'.$udom} = $outcome; + $inst_results->{$uname.':'.$udom} = $results->{$uname}; + } + } + } + } + } + } else { + foreach my $udom (keys(%by_username)) { + my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_username{$udom}); + if ($outcome eq 'ok') { + foreach my $uname (keys(%{$by_username{$udom}})) { + $inst_response{$uname.':'.$udom} = $outcome; + } + if (ref($results) eq 'HASH') { + foreach my $uname (keys(%{$results})) { + $inst_results->{$uname.':'.$udom} = $results->{$uname}; + } + } + } + } + } + } elsif (keys(%{$usershash}) == 1) { + my $user = (keys(%{$usershash}))[0]; + my ($uname,$udom) = split(/:/,$user); + if (($udom ne '') && ($uname ne '')) { + if (ref($usershash->{$user}) eq 'HASH') { + if (ref($checks) eq 'HASH') { + if (defined($checks->{'username'})) { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + } elsif (defined($checks->{'id'})) { + if ($usershash->{$user}->{'id'} ne '') { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,undef, + $usershash->{$user}->{'id'}); + } else { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + } + } + } else { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + return; + } + if (!$got_rules->{$udom}) { + my %domconfig = &Apache::lonnet::get_dom('configuration', + ['usercreation'],$udom); + if (ref($domconfig{'usercreation'}) eq 'HASH') { + foreach my $item ('username','id') { + if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') { + $$curr_rules{$udom}{$item} = + $domconfig{'usercreation'}{$item.'_rule'}; + } + } + } + $got_rules->{$udom} = 1; + } + } + } else { + return; + } + } else { + return; + } + foreach my $user (keys(%{$usershash})) { + my ($uname,$udom) = split(/:/,$user); + next if (($udom eq '') || ($uname eq '')); + my $id; + if (ref($inst_results) eq 'HASH') { + if (ref($inst_results->{$user}) eq 'HASH') { + $id = $inst_results->{$user}->{'id'}; + } + } + if ($id eq '') { + if (ref($usershash->{$user})) { + $id = $usershash->{$user}->{'id'}; + } + } + foreach my $item (keys(%{$checks})) { + if (ref($$curr_rules{$udom}) eq 'HASH') { + if (ref($$curr_rules{$udom}{$item}) eq 'ARRAY') { + if (@{$$curr_rules{$udom}{$item}} > 0) { + my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item, + $$curr_rules{$udom}{$item}); + foreach my $rule (@{$$curr_rules{$udom}{$item}}) { + if ($rule_check{$rule}) { + $$rulematch{$user}{$item} = $rule; + if ($inst_response{$user} eq 'ok') { + if (ref($inst_results) eq 'HASH') { + if (ref($inst_results->{$user}) eq 'HASH') { + if (keys(%{$inst_results->{$user}}) == 0) { + $$alerts{$item}{$udom}{$uname} = 1; + } elsif ($item eq 'id') { + if ($inst_results->{$user}->{'id'} eq '') { + $$alerts{$item}{$udom}{$uname} = 1; + } + } + } + } + } + last; + } + } + } + } + } + } + } + } + return; +} + +sub user_rule_formats { + my ($domain,$domdesc,$curr_rules,$check) = @_; + my %text = ( + 'username' => 'Usernames', + 'id' => 'IDs', + ); + my $output; + my ($rules,$ruleorder) = &Apache::lonnet::inst_userrules($domain,$check); + if ((ref($rules) eq 'HASH') && (ref($ruleorder) eq 'ARRAY')) { + if (@{$ruleorder} > 0) { + $output = '
'. + &mt($text{$check}.' with the following format(s) may [_1]only[_2] be used for verified users at [_3]:', + '','',$domdesc). + '
    '; + foreach my $rule (@{$ruleorder}) { + if (ref($curr_rules) eq 'ARRAY') { + if (grep(/^\Q$rule\E$/,@{$curr_rules})) { + if (ref($rules->{$rule}) eq 'HASH') { + $output .= '
  • '.$rules->{$rule}{'name'}.': '. + $rules->{$rule}{'desc'}.'
  • '; + } + } + } + } + $output .= '
'; + } + } + return $output; +} + +sub instrule_disallow_msg { + my ($checkitem,$domdesc,$count,$mode) = @_; + my $response; + my %text = ( + item => 'username', + items => 'usernames', + match => 'matches', + do => 'does', + action => 'a username', + one => 'one', + ); + if ($count > 1) { + $text{'item'} = 'usernames'; + $text{'match'} ='match'; + $text{'do'} = 'do'; + $text{'action'} = 'usernames', + $text{'one'} = 'ones'; + } + if ($checkitem eq 'id') { + $text{'items'} = 'IDs'; + $text{'item'} = 'ID'; + $text{'action'} = 'an ID'; + if ($count > 1) { + $text{'item'} = 'IDs'; + $text{'action'} = 'IDs'; + } + } + $response = &mt("The $text{'item'} you chose $text{'match'} the format of $text{'items'} defined for [_1], but the $text{'item'} $text{'do'} not exist in the institutional directory.",''.$domdesc.'').'
'; + if ($mode eq 'upload') { + if ($checkitem eq 'username') { + $response .= &mt("You will need to modify your upload file so it will include $text{'action'} with a different format -- $text{'one'} that will not conflict with 'official' institutional $text{'items'}."); + } elsif ($checkitem eq 'id') { + $response .= &mt("Either upload a file which includes $text{'action'} with a different format -- $text{'one'} that will not conflict with 'official' institutional $text{'items'}, or when associating fields with data columns, omit an association for the Student/Employee ID field."); + } + } elsif ($mode eq 'selfcreate') { + if ($checkitem eq 'id') { + $response .= &mt("You must either choose $text{'action'} with a different format -- $text{'one'} that will not conflict with 'official' institutional $text{'items'}, or leave the ID field blank."); + } + } else { + if ($checkitem eq 'username') { + $response .= &mt("You must choose $text{'action'} with a different format -- $text{'one'} that will not conflict with 'official' institutional $text{'items'}."); + } elsif ($checkitem eq 'id') { + $response .= &mt("You must either choose $text{'action'} with a different format -- $text{'one'} that will not conflict with 'official' institutional $text{'items'}, or leave the ID field blank."); + } + } + return $response; +} + +sub personal_data_fieldtitles { + my %fieldtitles = &Apache::lonlocal::texthash ( + id => 'Student/Employee ID', + permanentemail => 'E-mail address', + lastname => 'Last Name', + firstname => 'First Name', + middlename => 'Middle Name', + generation => 'Generation', + gen => 'Generation', + inststatus => 'Affiliation', + ); + return %fieldtitles; +} + +sub sorted_inst_types { + my ($dom) = @_; + my ($usertypes,$order); + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + if (ref($domdefaults{'inststatus'}) eq 'HASH') { + $usertypes = $domdefaults{'inststatus'}{'inststatustypes'}; + $order = $domdefaults{'inststatus'}{'inststatusorder'}; + } else { + ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom); + } + my $othertitle = &mt('All users'); + if ($env{'request.course.id'}) { + $othertitle = &mt('Any users'); + } + my @types; + if (ref($order) eq 'ARRAY') { + @types = @{$order}; + } + if (@types == 0) { + if (ref($usertypes) eq 'HASH') { + @types = sort(keys(%{$usertypes})); + } + } + if (keys(%{$usertypes}) > 0) { + $othertitle = &mt('Other users'); + } + return ($othertitle,$usertypes,\@types); +} + +sub get_institutional_codes { + my ($settings,$allcourses,$LC_code) = @_; +# Get complete list of course sections to update + my @currsections = (); + my @currxlists = (); + my $coursecode = $$settings{'internal.coursecode'}; + + if ($$settings{'internal.sectionnums'} ne '') { + @currsections = split(/,/,$$settings{'internal.sectionnums'}); + } + + if ($$settings{'internal.crosslistings'} ne '') { + @currxlists = split(/,/,$$settings{'internal.crosslistings'}); + } + + if (@currxlists > 0) { + foreach (@currxlists) { + if (m/^([^:]+):(\w*)$/) { + unless (grep/^$1$/,@{$allcourses}) { + push(@{$allcourses},$1); + $$LC_code{$1} = $2; + } + } + } + } + + if (@currsections > 0) { + foreach (@currsections) { + if (m/^(\w+):(\w*)$/) { + my $sec = $coursecode.$1; + my $lc_sec = $2; + unless (grep/^$sec$/,@{$allcourses}) { + push(@{$allcourses},$sec); + $$LC_code{$sec} = $lc_sec; + } + } + } + } + return; +} + +sub get_standard_codeitems { + return ('Year','Semester','Department','Number','Section'); +} + +=pod + +=head1 Slot Helpers + +=over 4 + +=item * sorted_slots() + +Sorts an array of slot names in order of an optional sort key, +default sort is by slot start time (earliest first). + +Inputs: + +=over 4 + +slotsarr - Reference to array of unsorted slot names. + +slots - Reference to hash of hash, where outer hash keys are slot names. + +sortkey - Name of key in inner hash to be sorted on (e.g., starttime). + +=back + +Returns: + +=over 4 + +sorted - An array of slot names sorted by a specified sort key + (default sort key is start time of the slot). + +=back + +=cut + + +sub sorted_slots { + my ($slotsarr,$slots,$sortkey) = @_; + if ($sortkey eq '') { + $sortkey = 'starttime'; + } + my @sorted; + if ((ref($slotsarr) eq 'ARRAY') && (ref($slots) eq 'HASH')) { + @sorted = + sort { + if (ref($slots->{$a}) && ref($slots->{$b})) { + return $slots->{$a}{$sortkey} <=> $slots->{$b}{$sortkey} + } + if (ref($slots->{$a})) { return -1;} + if (ref($slots->{$b})) { return 1;} + return 0; + } @{$slotsarr}; + } + return @sorted; +} + +=pod + +=item * get_future_slots() + +Inputs: + +=over 4 + +cnum - course number + +cdom - course domain + +now - current UNIX time + +symb - optional symb + +=back + +Returns: + +=over 4 + +sorted_reservable - ref to array of student_schedulable slots currently + reservable, ordered by end date of reservation period. + +reservable_now - ref to hash of student_schedulable slots currently + reservable. + + Keys in inner hash are: + (a) symb: either blank or symb to which slot use is restricted. + (b) endreserve: end date of reservation period. + (c) uniqueperiod: start,end dates when slot is to be uniquely + selected. + +sorted_future - ref to array of student_schedulable slots reservable in + the future, ordered by start date of reservation period. + +future_reservable - ref to hash of student_schedulable slots reservable + in the future. + + Keys in inner hash are: + (a) symb: either blank or symb to which slot use is restricted. + (b) startreserve: start date of reservation period. + (c) uniqueperiod: start,end dates when slot is to be uniquely + selected. + +=back + +=cut + +sub get_future_slots { + my ($cnum,$cdom,$now,$symb) = @_; + my $map; + if ($symb) { + ($map) = &Apache::lonnet::decode_symb($symb); + } + my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future); + my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom); + foreach my $slot (keys(%slots)) { + next unless($slots{$slot}->{'type'} eq 'schedulable_student'); + if ($symb) { + if ($slots{$slot}->{'symb'} ne '') { + my $canuse; + my %oksymbs; + my @slotsymbs = split(/\s*,\s*/,$slots{$slot}->{'symb'}); + map { $oksymbs{$_} = 1; } @slotsymbs; + if ($oksymbs{$symb}) { + $canuse = 1; + } else { + foreach my $item (@slotsymbs) { + if ($item =~ /\.(page|sequence)$/) { + (undef,undef,my $sloturl) = &Apache::lonnet::decode_symb($item); + if (($map ne '') && ($map eq $sloturl)) { + $canuse = 1; + last; + } + } + } + } + next unless ($canuse); + } + } + if (($slots{$slot}->{'starttime'} > $now) && + ($slots{$slot}->{'endtime'} > $now)) { + if (($slots{$slot}->{'allowedsections'}) || ($slots{$slot}->{'allowedusers'})) { + my $userallowed = 0; + if ($slots{$slot}->{'allowedsections'}) { + my @allowed_sec = split(',',$slots{$slot}->{'allowedsections'}); + if (!defined($env{'request.role.sec'}) + && grep(/^No section assigned$/,@allowed_sec)) { + $userallowed=1; + } else { + if (grep(/^\Q$env{'request.role.sec'}\E$/,@allowed_sec)) { + $userallowed=1; + } + } + unless ($userallowed) { + if (defined($env{'request.course.groups'})) { + my @groups = split(/:/,$env{'request.course.groups'}); + foreach my $group (@groups) { + if (grep(/^\Q$group\E$/,@allowed_sec)) { + $userallowed=1; + last; + } + } + } + } + } + if ($slots{$slot}->{'allowedusers'}) { + my @allowed_users = split(',',$slots{$slot}->{'allowedusers'}); + my $user = $env{'user.name'}.':'.$env{'user.domain'}; + if (grep(/^\Q$user\E$/,@allowed_users)) { + $userallowed = 1; + } + } + next unless($userallowed); + } + my $startreserve = $slots{$slot}->{'startreserve'}; + my $endreserve = $slots{$slot}->{'endreserve'}; + my $symb = $slots{$slot}->{'symb'}; + my $uniqueperiod; + if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') { + $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}}); + } + if (($startreserve < $now) && + (!$endreserve || $endreserve > $now)) { + my $lastres = $endreserve; + if (!$lastres) { + $lastres = $slots{$slot}->{'starttime'}; + } + $reservable_now{$slot} = { + symb => $symb, + endreserve => $lastres, + uniqueperiod => $uniqueperiod, + }; + } elsif (($startreserve > $now) && + (!$endreserve || $endreserve > $startreserve)) { + $future_reservable{$slot} = { + symb => $symb, + startreserve => $startreserve, + uniqueperiod => $uniqueperiod, + }; + } + } + } + my @unsorted_reservable = keys(%reservable_now); + if (@unsorted_reservable > 0) { + @sorted_reservable = + &sorted_slots(\@unsorted_reservable,\%reservable_now,'endreserve'); + } + my @unsorted_future = keys(%future_reservable); + if (@unsorted_future > 0) { + @sorted_future = + &sorted_slots(\@unsorted_future,\%future_reservable,'startreserve'); + } + return (\@sorted_reservable,\%reservable_now,\@sorted_future,\%future_reservable); +} + +=pod + +=back + +=head1 HTTP Helpers + +=over 4 + +=item * &get_unprocessed_cgi($query,$possible_names) + +Modify the %env hash to contain unprocessed CGI form parameters held in +$query. The parameters listed in $possible_names (an array reference), +will be set in $env{'form.name'} if they do not already exist. + +Typically called with $ENV{'QUERY_STRING'} as the first parameter. +$possible_names is an ref to an array of form element names. As an example: +get_unprocessed_cgi($ENV{'QUERY_STRING'},['uname','udom']); +will result in $env{'form.uname'} and $env{'form.udom'} being set. + +=cut + sub get_unprocessed_cgi { my ($query,$possible_names)= @_; # $Apache::lonxml::debug=1; - foreach (split(/&/,$query)) { - my ($name, $value) = split(/=/,$_); - $name = &Apache::lonnet::unescape($name); + foreach my $pair (split(/&/,$query)) { + my ($name, $value) = split(/=/,$pair); + $name = &unescape($name); if (!defined($possible_names) || (grep {$_ eq $name} @$possible_names)) { $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg; - &Apache::lonxml::debug("Seting :$name: to :$value:"); - unless (defined($ENV{'form.'.$name})) { &add_to_env('form.'.$name,$value) }; + unless (defined($env{'form.'.$name})) { &add_to_env('form.'.$name,$value) }; } } } +=pod + +=item * &cacheheader() + +returns cache-controlling header code + +=cut + sub cacheheader { - unless ($ENV{'request.method'} eq 'GET') { return ''; } - my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); - my $output .=' + unless ($env{'request.method'} eq 'GET') { return ''; } + my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); + my $output .=' '; - return $output; + return $output; } +=pod + +=item * &no_cache($r) + +specifies header code to not have cache + +=cut + sub no_cache { - my ($r) = @_; - unless ($ENV{'request.method'} eq 'GET') { return ''; } - #my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime); - $r->no_cache(1); - $r->header_out("Pragma" => "no-cache"); - #$r->header_out("Expires" => $date); + my ($r) = @_; + if ($ENV{'REQUEST_METHOD'} ne 'GET' && + $env{'request.method'} ne 'GET') { return ''; } + my $date=strftime("%a, %d %b %Y %H:%M:%S GMT",gmtime(time)); + $r->no_cache(1); + $r->header_out("Expires" => $date); + $r->header_out("Pragma" => "no-cache"); +} + +sub content_type { + my ($r,$type,$charset) = @_; + if ($r) { + # Note that printout.pl calls this with undef for $r. + &no_cache($r); + } + if ($env{'browser.mathml'} && $type eq 'text/html') { $type='text/xml'; } + unless ($charset) { + $charset=&Apache::lonlocal::current_encoding; + } + if ($charset) { $type.='; charset='.$charset; } + if ($r) { + $r->content_type($type); + } else { + print("Content-type: $type\n\n"); + } } +=pod + +=item * &add_to_env($name,$value) + +adds $name to the %env hash with value +$value, if $name already exists, the entry is converted to an array +reference and $value is added to the array. + +=cut + sub add_to_env { my ($name,$value)=@_; - if (defined($ENV{$name})) { - if (ref($ENV{$name})) { + if (defined($env{$name})) { + if (ref($env{$name})) { #already have multiple values - push(@{ $ENV{$name} },$value); + push(@{ $env{$name} },$value); } else { #first time seeing multiple values, convert hash entry to an arrayref - my $first=$ENV{$name}; - undef($ENV{$name}); - push(@{ $ENV{$name} },$first,$value); + my $first=$env{$name}; + undef($env{$name}); + push(@{ $env{$name} },$first,$value); } } else { - $ENV{$name}=$value; + $env{$name}=$value; } } -#---CSV Upload/Handling functions +=pod + +=item * &get_env_multiple($name) + +gets $name from the %env hash, it seemlessly handles the cases where multiple +values may be defined and end up as an array ref. + +returns an array of values + +=cut + +sub get_env_multiple { + my ($name) = @_; + my @values; + if (defined($env{$name})) { + # exists is it an array + if (ref($env{$name})) { + @values=@{ $env{$name} }; + } else { + $values[0]=$env{$name}; + } + } + return(@values); +} + +# Looks at given dependencies, and returns something depending on the context. +# For coursedocs paste, returns (undef, $counter, $numpathchg, \%existing). +# For syllabus rewrites, returns (undef, $counter, $numpathchg, \%existing, \%mapping). +# For all other contexts, returns ($output, $counter, $numpathchg). +# $output: string with the HTML output. Can contain missing dependencies with an upload form, existing dependencies, and dependencies no longer in use. +# $counter: integer with the number of existing dependencies when no HTML output is returned, and the number of missing dependencies when an HTML output is returned. +# $numpathchg: integer with the number of cleaned up dependency paths. +# \%existing: hash reference clean path -> 1 only for existing dependencies. +# \%mapping: hash reference clean path -> original path for all dependencies. +# @param {string} actionurl - The path to the handler, indicative of the context. +# @param {string} state - Can contain HTML with hidden inputs that will be added to the output form. +# @param {hash reference} allfiles - List of file info from lonnet::extract_embedded_items +# @param {hash reference} codebase - undef, not modified by lonnet::extract_embedded_items ? +# @param {hash reference} args - More parameters ! Possible keys: error_on_invalid_names (boolean), ignore_remote_references (boolean), current_path (string), docs_url (string), docs_title (string), context (string) +# @return {Array} - array depending on the context (not a reference) +sub ask_for_embedded_content { + # NOTE: documentation was added afterwards, it could be wrong + my ($actionurl,$state,$allfiles,$codebase,$args)=@_; + my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges, + %currsubfile,%unused,$rem); + my $counter = 0; + my $numnew = 0; + my $numremref = 0; + my $numinvalid = 0; + my $numpathchg = 0; + my $numexisting = 0; + my $numunused = 0; + my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath,$cdom,$cnum, + $fileloc,$filename,$delete_output,$modify_output,$title,$symb,$path,$navmap); + my $heading = &mt('Upload embedded files'); + my $buttontext = &mt('Upload'); + + # fills these variables based on the context: + # $navmap, $cdom, $cnum, $udom, $uname, $url, $toplevel, $getpropath, + # $path, $fileloc, $title, $rem, $filename + if ($env{'request.course.id'}) { + if ($actionurl eq '/adm/dependencies') { + $navmap = Apache::lonnavmaps::navmap->new(); + } + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + } + if (($actionurl eq '/adm/portfolio') || + ($actionurl eq '/adm/coursegrp_portfolio')) { + my $current_path='/'; + if ($env{'form.currentpath'}) { + $current_path = $env{'form.currentpath'}; + } + if ($actionurl eq '/adm/coursegrp_portfolio') { + $udom = $cdom; + $uname = $cnum; + $url = '/userfiles/groups/'.$env{'form.group'}.'/portfolio'; + } else { + $udom = $env{'user.domain'}; + $uname = $env{'user.name'}; + $url = '/userfiles/portfolio'; + } + $toplevel = $url.'/'; + $url .= $current_path; + $getpropath = 1; + } elsif (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank') || + ($actionurl eq '/adm/imsimport')) { + my ($udom,$uname,$rest) = ($args->{'current_path'} =~ m{/priv/($match_domain)/($match_username)/?(.*)$}); + $url = $Apache::lonnet::perlvar{'lonDocRoot'}."/priv/$udom/$uname/"; + $toplevel = $url; + if ($rest ne '') { + $url .= $rest; + } + } elsif ($actionurl eq '/adm/coursedocs') { + if (ref($args) eq 'HASH') { + $url = $args->{'docs_url'}; + $toplevel = $url; + if ($args->{'context'} eq 'paste') { + ($cdom,$cnum) = ($url =~ m{^\Q/uploaded/\E($match_domain)/($match_courseid)/}); + ($path) = + ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/}); + $fileloc = &Apache::lonnet::filelocation('',$toplevel); + $fileloc =~ s{^/}{}; + } + } + } elsif ($actionurl eq '/adm/dependencies') { + if ($env{'request.course.id'} ne '') { + if (ref($args) eq 'HASH') { + $url = $args->{'docs_url'}; + $title = $args->{'docs_title'}; + $toplevel = $url; + unless ($toplevel =~ m{^/}) { + $toplevel = "/$url"; + } + ($rem) = ($toplevel =~ m{^(.+/)[^/]+$}); + if ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/portfolio/syllabus\E)}) { + $path = $1; + } else { + ($path) = + ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/}); + } + if ($toplevel=~/^\/*(uploaded|editupload)/) { + $fileloc = $toplevel; + $fileloc=~ s/^\s*(\S+)\s*$/$1/; + my ($udom,$uname,$fname) = + ($fileloc=~ m{^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$}); + $fileloc = propath($udom,$uname).'/userfiles/'.$fname; + } else { + $fileloc = &Apache::lonnet::filelocation('',$toplevel); + } + $fileloc =~ s{^/}{}; + ($filename) = ($fileloc =~ m{.+/([^/]+)$}); + $heading = &mt('Status of dependencies in [_1]',"$title ($filename)"); + } + } + } elsif ($actionurl eq "/public/$cdom/$cnum/syllabus") { + $udom = $cdom; + $uname = $cnum; + $url = "/uploaded/$cdom/$cnum/portfolio/syllabus"; + $toplevel = $url; + $path = $url; + $fileloc = &Apache::lonnet::filelocation('',$toplevel).'/'; + $fileloc =~ s{^/}{}; + } + + # parses the dependency paths to get some info + # fills $newfiles, $mapping, $subdependencies, $dependencies + # $newfiles: hash URL -> 1 for new files or external URLs + # (will be completed later) + # $mapping: + # for external URLs: external URL -> external URL + # for relative paths: clean path -> original path + # $subdependencies: hash clean path -> clean file name -> 1 for relative paths in subdirectories + # $dependencies: hash clean or not file name -> 1 for relative paths not in subdirectories + foreach my $file (keys(%{$allfiles})) { + my $embed_file; + if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) { + $embed_file = $1; + } else { + $embed_file = $file; + } + my ($absolutepath,$cleaned_file); + if ($embed_file =~ m{^\w+://}) { + $cleaned_file = $embed_file; + $newfiles{$cleaned_file} = 1; + $mapping{$cleaned_file} = $embed_file; + } else { + $cleaned_file = &clean_path($embed_file); + if ($embed_file =~ m{^/}) { + $absolutepath = $embed_file; + } + if ($cleaned_file =~ m{/}) { + my ($path,$fname) = ($cleaned_file =~ m{^(.+)/([^/]*)$}); + $path = &check_for_traversal($path,$url,$toplevel); + my $item = $fname; + if ($path ne '') { + $item = $path.'/'.$fname; + $subdependencies{$path}{$fname} = 1; + } else { + $dependencies{$item} = 1; + } + if ($absolutepath) { + $mapping{$item} = $absolutepath; + } else { + $mapping{$item} = $embed_file; + } + } else { + $dependencies{$embed_file} = 1; + if ($absolutepath) { + $mapping{$cleaned_file} = $absolutepath; + } else { + $mapping{$cleaned_file} = $embed_file; + } + } + } + } + + # looks for all existing files in dependency subdirectories (from $subdependencies filled above) + # and lists + # fills $currsubfile, $pathchanges, $existing, $numexisting, $newfiles, $unused + # $currsubfile: hash clean path -> file name -> 1 for all existing files in the path + # $pathchanges: hash clean path -> 1 if the file in subdirectory exists and + # the path had to be cleaned up + # $existing: hash clean path -> 1 if the file exists + # $numexisting: number of keys in $existing + # $newfiles: updated with clean path -> 1 for files in subdirectories that do not exist + # $unused: only for /adm/dependencies, hash clean path -> 1 for existing files in + # dependency subdirectories that are + # not listed as dependencies, with some exceptions using $rem + my $dirptr = 16384; + foreach my $path (keys(%subdependencies)) { + $currsubfile{$path} = {}; + if (($actionurl eq '/adm/portfolio') || + ($actionurl eq '/adm/coursegrp_portfolio')) { + my ($sublistref,$listerror) = + &Apache::lonnet::dirlist($url.$path,$udom,$uname,$getpropath); + if (ref($sublistref) eq 'ARRAY') { + foreach my $line (@{$sublistref}) { + my ($file_name,$rest) = split(/\&/,$line,2); + $currsubfile{$path}{$file_name} = 1; + } + } + } elsif (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank')) { + if (opendir(my $dir,$url.'/'.$path)) { + my @subdir_list = grep(!/^\./,readdir($dir)); + map {$currsubfile{$path}{$_} = 1;} @subdir_list; + } + } elsif (($actionurl eq '/adm/dependencies') || + (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') && + ($args->{'context'} eq 'paste')) || + ($actionurl eq "/public/$cdom/$cnum/syllabus")) { + if ($env{'request.course.id'} ne '') { + my $dir; + if ($actionurl eq "/public/$cdom/$cnum/syllabus") { + $dir = $fileloc; + } else { + ($dir) = ($fileloc =~ m{^(.+/)[^/]+$}); + } + if ($dir ne '') { + my ($sublistref,$listerror) = + &Apache::lonnet::dirlist($dir.$path,$cdom,$cnum,$getpropath,undef,'/'); + if (ref($sublistref) eq 'ARRAY') { + foreach my $line (@{$sublistref}) { + my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef,$size, + undef,$mtime)=split(/\&/,$line,12); + unless (($testdir&$dirptr) || + ($file_name =~ /^\.\.?$/)) { + $currsubfile{$path}{$file_name} = [$size,$mtime]; + } + } + } + } + } + } + foreach my $file (keys(%{$subdependencies{$path}})) { + if (exists($currsubfile{$path}{$file})) { + my $item = $path.'/'.$file; + unless ($mapping{$item} eq $item) { + $pathchanges{$item} = 1; + } + $existing{$item} = 1; + $numexisting ++; + } else { + $newfiles{$path.'/'.$file} = 1; + } + } + if ($actionurl eq '/adm/dependencies') { + foreach my $path (keys(%currsubfile)) { + if (ref($currsubfile{$path}) eq 'HASH') { + foreach my $file (keys(%{$currsubfile{$path}})) { + unless ($subdependencies{$path}{$file}) { + next if (($rem ne '') && + (($env{"httpref.$rem"."$path/$file"} ne '') || + (ref($navmap) && + (($navmap->getResourceByUrl($rem."$path/$file") ne '') || + (($file =~ /^(.*\.s?html?)\.bak$/i) && + ($navmap->getResourceByUrl($rem."$path/$1"))))))); + $unused{$path.'/'.$file} = 1; + } + } + } + } + } + } + + # fills $currfile, hash file name -> 1 or [$size,$mtime] + # for files in $url or $fileloc (target directory) in some contexts + my %currfile; + if (($actionurl eq '/adm/portfolio') || + ($actionurl eq '/adm/coursegrp_portfolio')) { + my ($dirlistref,$listerror) = + &Apache::lonnet::dirlist($url,$udom,$uname,$getpropath); + if (ref($dirlistref) eq 'ARRAY') { + foreach my $line (@{$dirlistref}) { + my ($file_name,$rest) = split(/\&/,$line,2); + $currfile{$file_name} = 1; + } + } + } elsif (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank')) { + if (opendir(my $dir,$url)) { + my @dir_list = grep(!/^\./,readdir($dir)); + map {$currfile{$_} = 1;} @dir_list; + } + } elsif (($actionurl eq '/adm/dependencies') || + (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') && + ($args->{'context'} eq 'paste')) || + ($actionurl eq "/public/$cdom/$cnum/syllabus")) { + if ($env{'request.course.id'} ne '') { + my ($dir) = ($fileloc =~ m{^(.+/)[^/]+$}); + if ($dir ne '') { + my ($dirlistref,$listerror) = + &Apache::lonnet::dirlist($dir,$cdom,$cnum,$getpropath,undef,'/'); + if (ref($dirlistref) eq 'ARRAY') { + foreach my $line (@{$dirlistref}) { + my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef, + $size,undef,$mtime)=split(/\&/,$line,12); + unless (($testdir&$dirptr) || + ($file_name =~ /^\.\.?$/)) { + $currfile{$file_name} = [$size,$mtime]; + } + } + } + } + } + } + # updates $pathchanges, $existing, $numexisting, $newfiles and $unused for files that + # are not in subdirectories, using $currfile + foreach my $file (keys(%dependencies)) { + if (exists($currfile{$file})) { + unless ($mapping{$file} eq $file) { + $pathchanges{$file} = 1; + } + $existing{$file} = 1; + $numexisting ++; + } else { + $newfiles{$file} = 1; + } + } + foreach my $file (keys(%currfile)) { + unless (($file eq $filename) || + ($file eq $filename.'.bak') || + ($dependencies{$file})) { + if ($actionurl eq '/adm/dependencies') { + unless ($toplevel =~ m{^\Q/uploaded/$cdom/$cnum/portfolio/syllabus\E}) { + next if (($rem ne '') && + (($env{"httpref.$rem".$file} ne '') || + (ref($navmap) && + (($navmap->getResourceByUrl($rem.$file) ne '') || + (($file =~ /^(.*\.s?html?)\.bak$/i) && + ($navmap->getResourceByUrl($rem.$1))))))); + } + } + $unused{$file} = 1; + } + } + + # returns some results for coursedocs paste and syllabus rewrites ($output is undef) + if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') && + ($args->{'context'} eq 'paste')) { + $counter = scalar(keys(%existing)); + $numpathchg = scalar(keys(%pathchanges)); + return ($output,$counter,$numpathchg,\%existing); + } elsif (($actionurl eq "/public/$cdom/$cnum/syllabus") && + (ref($args) eq 'HASH') && ($args->{'context'} eq 'rewrites')) { + $counter = scalar(keys(%existing)); + $numpathchg = scalar(keys(%pathchanges)); + return ($output,$counter,$numpathchg,\%existing,\%mapping); + } + + # returns HTML otherwise, with dependency results and to ask for more uploads + + # $upload_output: missing dependencies (with upload form) + # $modify_output: uploaded dependencies (in use) + # $delete_output: files no longer in use (unused files are not listed for londocs, bug?) + foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) { + if ($actionurl eq '/adm/dependencies') { + next if ($embed_file =~ m{^\w+://}); + } + $upload_output .= &start_data_table_row(). + ' '. + ''.$embed_file.''; + unless ($mapping{$embed_file} eq $embed_file) { + $upload_output .= '
'. + &mt('changed from: [_1]',$mapping{$embed_file}).''; + } + $upload_output .= ''; + if ($args->{'ignore_remote_references'} && $embed_file =~ m{^\w+://}) { + $upload_output.=''. + ''. + &mt("URL points to web address").''; + $numremref++; + } elsif ($args->{'error_on_invalid_names'} + && $embed_file ne &Apache::lonnet::clean_filename($embed_file,{'keep_path' => 1,})) { + $upload_output.=''. + &mt('Invalid characters').''; + $numinvalid++; + } else { + $upload_output .= ''. + &embedded_file_element('upload_embedded',$counter, + $embed_file,\%mapping, + $allfiles,$codebase,'upload'); + $counter ++; + $numnew ++; + } + $upload_output .= ''.&Apache::loncommon::end_data_table_row()."\n"; + } + foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%existing)) { + if ($actionurl eq '/adm/dependencies') { + my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$embed_file); + $modify_output .= &start_data_table_row(). + ''. + ''. + ' '.$embed_file.''. + ''.$size.''. + ''.$mtime.''. + ''. + ''. + &end_data_table_row()."\n"; + $counter ++; + } else { + $upload_output .= &start_data_table_row(). + ' '. + ''.$embed_file.''. + ''.&mt('Already exists').''. + &Apache::loncommon::end_data_table_row()."\n"; + } + } + my $delidx = $counter; + foreach my $oldfile (sort {lc($a) cmp lc($b)} keys(%unused)) { + my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$oldfile); + $delete_output .= &start_data_table_row(). + ''. + ' '.$oldfile.''. + ''.$size.''. + ''.$mtime.''. + ''. + &embedded_file_element('upload_embedded',$delidx, + $oldfile,\%mapping,$allfiles, + $codebase,'delete').''. + &end_data_table_row()."\n"; + $numunused ++; + $delidx ++; + } + if ($upload_output) { + $upload_output = &start_data_table(). + $upload_output. + &end_data_table()."\n"; + } + if ($modify_output) { + $modify_output = &start_data_table(). + &start_data_table_header_row(). + ''.&mt('File').''. + ''.&mt('Size (KB)').''. + ''.&mt('Modified').''. + ''.&mt('Upload replacement?').''. + &end_data_table_header_row(). + $modify_output. + &end_data_table()."\n"; + } + if ($delete_output) { + $delete_output = &start_data_table(). + &start_data_table_header_row(). + ''.&mt('File').''. + ''.&mt('Size (KB)').''. + ''.&mt('Modified').''. + ''.&mt('Delete?').''. + &end_data_table_header_row(). + $delete_output. + &end_data_table()."\n"; + } + my $applies = 0; + if ($numremref) { + $applies ++; + } + if ($numinvalid) { + $applies ++; + } + if ($numexisting) { + $applies ++; + } + if ($counter || $numunused) { + $output = ''."\n". + $state.'

'.$heading.'

'; + if ($actionurl eq '/adm/dependencies') { + if ($numnew) { + $output .= '

'.&mt('Missing dependencies').'

'. + '

'.&mt('The following files need to be uploaded.').'

'."\n". + $upload_output.'
'."\n"; + } + if ($numexisting) { + $output .= '

'.&mt('Uploaded dependencies (in use)').'

'. + '

'.&mt('Upload a new file to replace the one currently in use.').'

'."\n". + $modify_output.'
'."\n"; + $buttontext = &mt('Save changes'); + } + if ($numunused) { + $output .= '

'.&mt('Unused files').'

'. + '

'.&mt('The following uploaded files are no longer used.').'

'."\n". + $delete_output.'
'."\n"; + $buttontext = &mt('Save changes'); + } + } else { + $output .= $upload_output.'
'."\n"; + } + $output .= ''."\n"; + if ($actionurl eq '/adm/dependencies') { + $output .= ''."\n"; + } elsif ($actionurl eq '') { + $output .= ''; + } + } elsif ($applies) { + $output = ''.&mt('Referenced files').':
'; + if ($applies > 1) { + $output .= + &mt('No dependencies need to be uploaded, as one of the following applies to each reference:').'
    '; + if ($numremref) { + $output .= '
  • '.&mt('reference is to a URL which points to another server').'
  • '."\n"; + } + if ($numinvalid) { + $output .= '
  • '.&mt('reference is to file with a name containing invalid characters').'
  • '."\n"; + } + if ($numexisting) { + $output .= '
  • '.&mt('reference is to an existing file at the specified location').'
  • '."\n"; + } + $output .= '

'; + } elsif ($numremref) { + $output .= '

'.&mt('None to upload, as all references are to URLs pointing to another server.').'

'; + } elsif ($numinvalid) { + $output .= '

'.&mt('None to upload, as all references are to files with names containing invalid characters.').'

'; + } elsif ($numexisting) { + $output .= '

'.&mt('None to upload, as all references are to existing files.').'

'; + } + $output .= $upload_output.'
'; + } + my ($pathchange_output,$chgcount); + $chgcount = $counter; + if (keys(%pathchanges) > 0) { + foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%pathchanges)) { + if ($counter) { + $output .= &embedded_file_element('pathchange',$chgcount, + $embed_file,\%mapping, + $allfiles,$codebase,'change'); + } else { + $pathchange_output .= + &start_data_table_row(). + ''. + ''.$mapping{$embed_file}.''. + ''.$embed_file. + &embedded_file_element('pathchange',$numpathchg,$embed_file, + \%mapping,$allfiles,$codebase,'change'). + ''.&end_data_table_row(); + } + $numpathchg ++; + $chgcount ++; + } + } + if (($counter) || ($numunused)) { + if ($numpathchg) { + $output .= ''."\n"; + } + if (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank') || + ($actionurl eq '/adm/imsimport')) { + $output .= ''."\n"; + } elsif ($actionurl eq '/adm/portfolio' || $actionurl eq '/adm/coursegrp_portfolio') { + $output .= ''; + } elsif ($actionurl eq '/adm/dependencies') { + $output .= ''; + } + $output .= ''."\n".''."\n"; + } elsif ($numpathchg) { + my %pathchange = (); + $output .= &modify_html_form('pathchange',$actionurl,$state,\%pathchange,$pathchange_output); + if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) { + $output .= '

'.&mt('or').'

'; + } + } + return ($output,$counter,$numpathchg); +} + +=pod + +=item * clean_path($name) + +Performs clean-up of directories, subdirectories and filename in an +embedded object, referenced in an HTML file which is being uploaded +to a course or portfolio, where +"Upload embedded images/multimedia files if HTML file" checkbox was +checked. + +Clean-up is similar to replacements in lonnet::clean_filename() +except each / between sub-directory and next level is preserved. + +=cut + +sub clean_path { + my ($embed_file) = @_; + $embed_file =~s{^/+}{}; + my @contents; + if ($embed_file =~ m{/}) { + @contents = split(/\//,$embed_file); + } else { + @contents = ($embed_file); + } + my $lastidx = scalar(@contents)-1; + for (my $i=0; $i<=$lastidx; $i++) { + $contents[$i]=~s{\\}{/}g; + $contents[$i]=~s/\s+/\_/g; + $contents[$i]=~s{[^/\w\.\-]}{}g; + if ($i == $lastidx) { + $contents[$i]=~s/\.(\d+)(?=\.)/_$1/g; + } + } + if ($lastidx > 0) { + return join('/',@contents); + } else { + return $contents[0]; + } +} + +sub embedded_file_element { + my ($context,$num,$embed_file,$mapping,$allfiles,$codebase,$type) = @_; + return unless ((ref($mapping) eq 'HASH') && (ref($allfiles) eq 'HASH') && + (ref($codebase) eq 'HASH')); + my $output; + if (($context eq 'upload_embedded') && ($type ne 'delete')) { + $output = ''."\n"; + } + $output .= ''; + unless (($context eq 'upload_embedded') && + ($mapping->{$embed_file} eq $embed_file)) { + $output .=' + '; + } + my $attrib; + if (ref($allfiles->{$mapping->{$embed_file}}) eq 'ARRAY') { + $attrib = &escape(join(':',@{$allfiles->{$mapping->{$embed_file}}})); + } + $output .= + "\n\t\t". + ''; + if (exists($codebase->{$mapping->{$embed_file}})) { + $output .= + "\n\t\t". + ''; + } + return $output; +} + +sub get_dependency_details { + my ($currfile,$currsubfile,$embed_file) = @_; + my ($size,$mtime,$showsize,$showmtime); + if ((ref($currfile) eq 'HASH') && (ref($currsubfile))) { + if ($embed_file =~ m{/}) { + my ($path,$fname) = split(/\//,$embed_file); + if (ref($currsubfile->{$path}{$fname}) eq 'ARRAY') { + ($size,$mtime) = @{$currsubfile->{$path}{$fname}}; + } + } else { + if (ref($currfile->{$embed_file}) eq 'ARRAY') { + ($size,$mtime) = @{$currfile->{$embed_file}}; + } + } + $showsize = $size/1024.0; + $showsize = sprintf("%.1f",$showsize); + if ($mtime > 0) { + $showmtime = &Apache::lonlocal::locallocaltime($mtime); + } + } + return ($showsize,$showmtime); +} + +sub ask_embedded_js { + return <<"END"; + + +END +} + +sub upload_embedded { + my ($context,$dirpath,$uname,$udom,$dir_root,$url_root,$group,$disk_quota, + $current_disk_usage,$hiddenstate,$actionurl) = @_; + my (%pathchange,$output,$modifyform,$footer,$returnflag); + 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'}; + foreach my $type ('orig','ref','attrib','codebase') { + if ($env{'form.embedded_'.$type.'_'.$i} ne '') { + $env{'form.embedded_'.$type.'_'.$i} = + &unescape($env{'form.embedded_'.$type.'_'.$i}); + } + } + my ($path,$fname) = + ($env{'form.embedded_orig_'.$i} =~ m{(.*/)([^/]*)}); + # no path, whole string is fname + if (!$fname) { $fname = $env{'form.embedded_orig_'.$i} }; + $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($env{'form.currentpath'}.$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') { + $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 internal use.',$1) + .' '.&mt('Rename the file with a different extension and re-upload.').'
'; + 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('Filename 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; + my $subdir = $path; + $subdir =~ s{/+$}{}; + if ($context eq 'portfolio') { + my $result; + if ($state eq 'existingfile') { + $result= + &Apache::lonnet::userfileupload('embedded_item_'.$i,'existingfile', + $dirpath.$env{'form.currentpath'}.$subdir); + } else { + $result= + &Apache::lonnet::userfileupload('embedded_item_'.$i,'', + $dirpath. + $env{'form.currentpath'}.$subdir); + if ($result !~ m|^/uploaded/|) { + $output .= '' + .&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].' + ,$result,$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}) + .'
'; + next; + } else { + $output .= &mt('Uploaded [_1]',''. + $path.$fname.'').'
'; + } + } + } elsif (($context eq 'coursedoc') || ($context eq 'syllabus')) { + my $extendedsubdir = $dirpath.'/'.$subdir; + $extendedsubdir =~ s{/+$}{}; + my $result = + &Apache::lonnet::userfileupload('embedded_item_'.$i,$context,$extendedsubdir); + if ($result !~ m|^/uploaded/|) { + $output .= '' + .&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].' + ,$result,$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}) + .'
'; + next; + } else { + $output .= &mt('Uploaded [_1]',''. + $path.$fname.'').'
'; + if ($context eq 'syllabus') { + &Apache::lonnet::make_public_indefinitely($result); + } + } + } 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(/\//,"$dirpath/$path"); + my $count; + my $filepath = $dir_root; + foreach my $subdir (@parts) { + $filepath .= "/$subdir"; + if (!-e $filepath) { + mkdir($filepath,0770); + } + } + my $fh; + if (!open($fh,'>'.$dest)) { + &Apache::lonnet::logthis('Failed to create '.$dest); + $output .= ''. + &mt('An error occurred while trying to upload [_1] for embedded element [_2].', + $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}). + '
'; + } else { + if (!print $fh $env{'form.embedded_item_'.$i}) { + &Apache::lonnet::logthis('Failed to write to '.$dest); + $output .= ''. + &mt('An error occurred while writing the file [_1] for embedded element [_2].', + $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}). + '
'; + } else { + $output .= &mt('Uploaded [_1]',''. + $url.'').'
'; + unless ($context eq 'testbank') { + $footer .= &mt('View embedded file: [_1]', + ''.$fname.'').'
'; + } + } + close($fh); + } + } + if ($env{'form.embedded_ref_'.$i}) { + $pathchange{$i} = 1; + } + } + if ($output) { + $output = '

'.$output.'

'; + } + $output .= &modify_html_form('upload_embedded',$actionurl,$hiddenstate,\%pathchange); + $returnflag = 'ok'; + my $numpathchgs = scalar(keys(%pathchange)); + if ($numpathchgs > 0) { + if ($context eq 'portfolio') { + $output .= '

'.&mt('or').'

'; + } elsif ($context eq 'testbank') { + $output .= '

'.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).', + '','').'

'; + $returnflag = 'modify_orightml'; + } + } + return ($output.$footer,$returnflag,$numpathchgs); +} + +sub modify_html_form { + my ($context,$actionurl,$hiddenstate,$pathchange,$pathchgtable) = @_; + my $end = 0; + my $modifyform; + if ($context eq 'upload_embedded') { + return unless (ref($pathchange) eq 'HASH'); + if ($env{'form.number_embedded_items'}) { + $end += $env{'form.number_embedded_items'}; + } + if ($env{'form.number_pathchange_items'}) { + $end += $env{'form.number_pathchange_items'}; + } + if ($end) { + for (my $i=0; $i<$end; $i++) { + if ($i < $env{'form.number_embedded_items'}) { + next unless($pathchange->{$i}); + } + $modifyform .= + &start_data_table_row(). + ''. + ''.$env{'form.embedded_ref_'.$i}. + ''. + ''. + ''. + ''.$env{'form.embedded_orig_'.$i}. + ''. + &end_data_table_row(); + } + } + } else { + $modifyform = $pathchgtable; + if (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank')) { + $hiddenstate .= ''; + } elsif (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) { + $hiddenstate .= ''; + } + } + if ($modifyform) { + if ($actionurl eq '/adm/dependencies') { + $hiddenstate .= ''; + } + return '

'.&mt('Changes in content of HTML file required').'

'."\n". + '

'.&mt('Changes need to be made to the reference(s) used for one or more of the dependencies, if your HTML file is to work correctly:').'

    '."\n". + '
  1. '.&mt('For consistency between the reference(s) and the location of the corresponding stored file within LON-CAPA.').'
  2. '."\n". + '
  3. '.&mt('To change absolute paths to relative paths, or replace directory traversal via "../" within the original reference.').'
  4. '."\n". + '

'."\n".'

'. + &mt('LON-CAPA can make the required changes to your HTML file.').'

'."\n". + '
'. + &start_data_table()."\n". + &start_data_table_header_row(). + ''.&mt('Change?').''. + ''.&mt('Current reference').''. + ''.&mt('Required reference').''. + &end_data_table_header_row()."\n". + $modifyform. + &end_data_table().'
'."\n".$hiddenstate. + ''. + '
'."\n"; + } + return; +} + +sub modify_html_refs { + my ($context,$dirpath,$uname,$udom,$dir_root,$url) = @_; + my $container; + if ($context eq 'portfolio') { + $container = $env{'form.container'}; + } elsif ($context eq 'coursedoc') { + $container = $env{'form.primaryurl'}; + } elsif ($context eq 'manage_dependencies') { + (undef,undef,$container) = &Apache::lonnet::decode_symb($env{'form.symb'}); + $container = "/$container"; + } elsif ($context eq 'syllabus') { + $container = $url; + } else { + $container = $Apache::lonnet::perlvar{'lonDocRoot'}.$env{'form.filename'}; + } + my (%allfiles,%codebase,$output,$content); + my @changes = &get_env_multiple('form.namechange'); + unless ((@changes > 0) || ($context eq 'syllabus')) { + if (wantarray) { + return ('',0,0); + } else { + return; + } + } + if (($context eq 'portfolio') || ($context eq 'coursedoc') || + ($context eq 'manage_dependencies') || ($context eq 'syllabus')) { + unless ($container =~ m{^/uploaded/\Q$udom\E/\Q$uname\E/}) { + if (wantarray) { + return ('',0,0); + } else { + return; + } + } + $content = &Apache::lonnet::getfile($container); + if ($content eq '-1') { + if (wantarray) { + return ('',0,0); + } else { + return; + } + } + } else { + unless ($container =~ /^\Q$dir_root\E/) { + if (wantarray) { + return ('',0,0); + } else { + return; + } + } + if (open(my $fh,"<$container")) { + $content = join('', <$fh>); + close($fh); + } else { + if (wantarray) { + return ('',0,0); + } else { + return; + } + } + } + my ($count,$codebasecount) = (0,0); + my $mm = new File::MMagic; + my $mime_type = $mm->checktype_contents($content); + if ($mime_type eq 'text/html') { + my $parse_result = + &Apache::lonnet::extract_embedded_items($container,\%allfiles, + \%codebase,\$content); + if ($parse_result eq 'ok') { + foreach my $i (@changes) { + my $orig = &unescape($env{'form.embedded_orig_'.$i}); + my $ref = &unescape($env{'form.embedded_ref_'.$i}); + if ($allfiles{$ref}) { + my $newname = $orig; + my ($attrib_regexp,$codebase); + $attrib_regexp = &unescape($env{'form.embedded_attrib_'.$i}); + if ($attrib_regexp =~ /:/) { + $attrib_regexp =~ s/\:/|/g; + } + if ($content =~ m{($attrib_regexp\s*=\s*['"]?)\Q$ref\E(['"]?)}) { + my $numchg = ($content =~ s{($attrib_regexp\s*=\s*['"]?)\Q$ref\E(['"]?)}{$1$newname$2}gi); + $count += $numchg; + $allfiles{$newname} = $allfiles{$ref}; + delete($allfiles{$ref}); + } + if ($env{'form.embedded_codebase_'.$i} ne '') { + $codebase = &unescape($env{'form.embedded_codebase_'.$i}); + my $numchg = ($content =~ s/(codebase\s*=\s*["']?)\Q$codebase\E(["']?)/$1.$2/i); #' stupid emacs + $codebasecount ++; + } + } + } + my $skiprewrites; + if ($count || $codebasecount) { + my $saveresult; + if (($context eq 'portfolio') || ($context eq 'coursedoc') || + ($context eq 'manage_dependencies') || ($context eq 'syllabus')) { + my $url = &Apache::lonnet::store_edited_file($container,$content,$udom,$uname,\$saveresult); + if ($url eq $container) { + my ($fname) = ($container =~ m{/([^/]+)$}); + $output = '

'.&mt('Updated [quant,_1,reference] in [_2].', + $count,''. + $fname.'').'

'; + } else { + $output = '

'. + &mt('Error: update failed for: [_1].', + ''. + $container.'').'

'; + } + if ($context eq 'syllabus') { + unless ($saveresult eq 'ok') { + $skiprewrites = 1; + } + } + } else { + if (open(my $fh,">$container")) { + print $fh $content; + close($fh); + $output = '

'.&mt('Updated [quant,_1,reference] in [_2].', + $count,''. + $container.'').'

'; + } else { + $output = '

'. + &mt('Error: could not update [_1].', + ''. + $container.'').'

'; + } + } + } + if (($context eq 'syllabus') && (!$skiprewrites)) { + my ($actionurl,$state); + $actionurl = "/public/$udom/$uname/syllabus"; + my ($ignore,$num,$numpathchanges,$existing,$mapping) = + &ask_for_embedded_content($actionurl,$state,\%allfiles, + \%codebase, + {'context' => 'rewrites', + 'ignore_remote_references' => 1,}); + if (ref($mapping) eq 'HASH') { + my $rewrites = 0; + foreach my $key (keys(%{$mapping})) { + next if ($key =~ m{^https?://}); + my $ref = $mapping->{$key}; + my $newname = "/uploaded/$udom/$uname/portfolio/syllabus/$key"; + my $attrib; + if (ref($allfiles{$mapping->{$key}}) eq 'ARRAY') { + $attrib = join('|',@{$allfiles{$mapping->{$key}}}); + } + if ($content =~ m{($attrib\s*=\s*['"]?)\Q$ref\E(['"]?)}) { + my $numchg = ($content =~ s{($attrib\s*=\s*['"]?)\Q$ref\E(['"]?)}{$1$newname$2}gi); + $rewrites += $numchg; + } + } + if ($rewrites) { + my $saveresult; + my $url = &Apache::lonnet::store_edited_file($container,$content,$udom,$uname,\$saveresult); + if ($url eq $container) { + my ($fname) = ($container =~ m{/([^/]+)$}); + $output .= '

'.&mt('Rewrote [quant,_1,link] as [quant,_1,absolute link] in [_2].', + $count,''. + $fname.'').'

'; + } else { + $output .= '

'. + &mt('Error: could not update links in [_1].', + ''. + $container.'').'

'; + + } + } + } + } + } else { + &logthis('Failed to parse '.$container. + ' to modify references: '.$parse_result); + } + } + if (wantarray) { + return ($output,$count,$codebasecount); + } else { + 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].',''.$fname.'',$path); + } elsif (-e $path.'/'.$fname) { + $state = 'exists'; + $msg = &mt('Unable to upload [_1]. A file by that name was found in [_2].',''.$fname.'',$path); + } + if ($state eq 'exists') { + $msg = ''.$msg.'
'; + } + 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}); + if (!$filesize) { + my $msg = ''. + &mt('Unable to upload [_1]. (size = [_2] bytes)', + ''.$fname.'', + $filesize).'
'. + &mt('Either the file you attempted to upload was empty, or your web browser was unable to read its contents.').'
'. + '
'; + return ('zero_bytes',$msg); + } + $filesize = $filesize/1000; #express in k (1024?) + my $getpropath = 1; + my ($dirlistref,$listerror) = + &Apache::lonnet::dirlist($portfolio_root.$path,$udom,$uname,$getpropath); + my $found_file = 0; + my $locked_file = 0; + my @lockers; + my $navmap; + if ($env{'request.course.id'}) { + $navmap = Apache::lonnavmaps::navmap->new(); + } + if (ref($dirlistref) eq 'ARRAY') { + foreach my $line (@{$dirlistref}) { + my ($file_name,$rest)=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,\@lockers) eq 'true') { + foreach my $lock (@lockers) { + if (ref($lock) eq 'ARRAY') { + my ($symb,$crsid) = @{$lock}; + if ($crsid eq $env{'request.course.id'}) { + if (ref($navmap)) { + my $res = $navmap->getBySymb($symb); + foreach my $part (@{$res->parts()}) { + my ($slot_status,$slot_time,$slot_name)=$res->check_for_slot($part); + unless (($slot_status == $res->RESERVED) || + ($slot_status == $res->RESERVED_LOCATION)) { + $locked_file = 1; + } + } + } else { + $locked_file = 1; + } + } else { + $locked_file = 1; + } + } + } + } else { + my @info = split(/\&/,$rest); + my $currsize = $info[6]/1000; + if ($currsize < $filesize) { + my $extra = $filesize - $currsize; + if (($current_disk_usage + $extra) > $disk_quota) { + my $msg = '

'. + &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded if existing (smaller) file with same name (size = [_3] kilobytes) is replaced.', + ''.$fname.'',$filesize,$currsize).'

'. + '

'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.', + $disk_quota,$current_disk_usage).'

'; + return ('will_exceed_quota',$msg); + } + } + } + } + } + } + if (($current_disk_usage + $filesize) > $disk_quota){ + my $msg = '

'. + &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.',''.$fname.'',$filesize).'

'. + '

'.&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 = '

'; + $msg .= &mt('Unable to upload [_1]. A locked file by that name was found in [_2].',''.$fname.'',''.$port_path.$env{'form.currentpath'}.''); + $msg .= '

'; + $msg .= &mt('You will be able to rename or delete existing [_1] after a grade has been assigned.',''.$fname.''); + return ('file_locked',$msg); + } else { + my $msg = '

'; + $msg .= &mt(' A file by that name: [_1] was found in [_2].',''.$fname.'',$port_path.$env{'form.currentpath'}); + $msg .= '

'; + return ('existingfile',$msg); + } + } +} + +sub check_for_traversal { + my ($path,$url,$toplevel) = @_; + my @parts=split(/\//,$path); + my $cleanpath; + my $fullpath = $url; + for (my $i=0;$i<@parts;$i++) { + next if ($parts[$i] eq '.'); + if ($parts[$i] eq '..') { + $fullpath =~ s{([^/]+/)$}{}; + } else { + $fullpath .= $parts[$i].'/'; + } + } + if ($fullpath =~ /^\Q$url\E(.*)$/) { + $cleanpath = $1; + } elsif ($fullpath =~ /^\Q$toplevel\E(.*)$/) { + my $curr_toprel = $1; + my @parts = split(/\//,$curr_toprel); + my ($url_toprel) = ($url =~ /^\Q$toplevel\E(.*)$/); + my @urlparts = split(/\//,$url_toprel); + my $doubledots; + my $startdiff = -1; + for (my $i=0; $i<@urlparts; $i++) { + if ($startdiff == -1) { + unless ($urlparts[$i] eq $parts[$i]) { + $startdiff = $i; + $doubledots .= '../'; + } + } else { + $doubledots .= '../'; + } + } + if ($startdiff > -1) { + $cleanpath = $doubledots; + for (my $i=$startdiff; $i<@parts; $i++) { + $cleanpath .= $parts[$i].'/'; + } + } + } + $cleanpath =~ s{(/)$}{}; + return $cleanpath; +} + +sub is_archive_file { + my ($mimetype) = @_; + if (($mimetype eq 'application/octet-stream') || + ($mimetype eq 'application/x-stuffit') || + ($mimetype =~ m{^application/(x\-)?(compressed|tar|zip|tgz|gz|gtar|gzip|gunzip|bz|bz2|bzip2)})) { + return 1; + } + return; +} + +sub decompress_form { + my ($mimetype,$archiveurl,$action,$noextract,$hiddenelements,$dirlist) = @_; + my %lt = &Apache::lonlocal::texthash ( + this => 'This file is an archive file.', + camt => 'This file is a Camtasia archive file.', + itsc => 'Its contents are as follows:', + youm => 'You may wish to extract its contents.', + extr => 'Extract contents', + auto => 'LON-CAPA can process the files automatically, or you can decide how each should be handled.', + proa => 'Process automatically?', + yes => 'Yes', + no => 'No', + fold => 'Title for folder containing movie', + movi => 'Title for page containing embedded movie', + ); + my $fileloc = &Apache::lonnet::filelocation(undef,$archiveurl); + my ($is_camtasia,$topdir,%toplevel,@paths); + my $info = &list_archive_contents($fileloc,\@paths); + if (@paths) { + foreach my $path (@paths) { + $path =~ s{^/}{}; + if ($path =~ m{^([^/]+)/$}) { + $topdir = $1; + } + if ($path =~ m{^([^/]+)/}) { + $toplevel{$1} = $path; + } else { + $toplevel{$path} = $path; + } + } + } + if ($mimetype =~ m{^application/(x\-)?(compressed|zip)}) { + my @camtasia6 = ("$topdir/","$topdir/index.html", + "$topdir/media/", + "$topdir/media/$topdir.mp4", + "$topdir/media/FirstFrame.png", + "$topdir/media/player.swf", + "$topdir/media/swfobject.js", + "$topdir/media/expressInstall.swf"); + my @camtasia8_1 = ("$topdir/","$topdir/$topdir.html", + "$topdir/$topdir.mp4", + "$topdir/$topdir\_config.xml", + "$topdir/$topdir\_controller.swf", + "$topdir/$topdir\_embed.css", + "$topdir/$topdir\_First_Frame.png", + "$topdir/$topdir\_player.html", + "$topdir/$topdir\_Thumbnails.png", + "$topdir/playerProductInstall.swf", + "$topdir/scripts/", + "$topdir/scripts/config_xml.js", + "$topdir/scripts/handlebars.js", + "$topdir/scripts/jquery-1.7.1.min.js", + "$topdir/scripts/jquery-ui-1.8.15.custom.min.js", + "$topdir/scripts/modernizr.js", + "$topdir/scripts/player-min.js", + "$topdir/scripts/swfobject.js", + "$topdir/skins/", + "$topdir/skins/configuration_express.xml", + "$topdir/skins/express_show/", + "$topdir/skins/express_show/player-min.css", + "$topdir/skins/express_show/spritesheet.png"); + my @camtasia8_4 = ("$topdir/","$topdir/$topdir.html", + "$topdir/$topdir.mp4", + "$topdir/$topdir\_config.xml", + "$topdir/$topdir\_controller.swf", + "$topdir/$topdir\_embed.css", + "$topdir/$topdir\_First_Frame.png", + "$topdir/$topdir\_player.html", + "$topdir/$topdir\_Thumbnails.png", + "$topdir/playerProductInstall.swf", + "$topdir/scripts/", + "$topdir/scripts/config_xml.js", + "$topdir/scripts/techsmith-smart-player.min.js", + "$topdir/skins/", + "$topdir/skins/configuration_express.xml", + "$topdir/skins/express_show/", + "$topdir/skins/express_show/spritesheet.min.css", + "$topdir/skins/express_show/spritesheet.png", + "$topdir/skins/express_show/techsmith-smart-player.min.css"); + my @diffs = &compare_arrays(\@paths,\@camtasia6); + if (@diffs == 0) { + $is_camtasia = 6; + } else { + @diffs = &compare_arrays(\@paths,\@camtasia8_1); + if (@diffs == 0) { + $is_camtasia = 8; + } else { + @diffs = &compare_arrays(\@paths,\@camtasia8_4); + if (@diffs == 0) { + $is_camtasia = 8; + } + } + } + } + my $output; + if ($is_camtasia) { + $output = <<"ENDCAM"; + +

$lt{'camt'}

+ENDCAM + } else { + $output = '

'.$lt{'this'}; + if ($info eq '') { + $output .= ' '.$lt{'youm'}.'

'."\n"; + } else { + $output .= ' '.$lt{'itsc'}.'

'."\n". + '
'.$info.'
'; + } + } + $output .= '
'."\n"; + my $duplicates; + my $num = 0; + if (ref($dirlist) eq 'ARRAY') { + foreach my $item (@{$dirlist}) { + if (ref($item) eq 'ARRAY') { + if (exists($toplevel{$item->[0]})) { + $duplicates .= + &start_data_table_row(). + ''. + ' '. + ''."\n". + ''.$item->[0].''; + if ($item->[2]) { + $duplicates .= ''.&mt('Directory').''; + } else { + $duplicates .= ''.&mt('File').''; + } + $duplicates .= ''.$item->[3].''. + ''. + &Apache::lonlocal::locallocaltime($item->[4]). + ''. + &end_data_table_row(); + $num ++; + } + } + } + } + my $itemcount; + if (@paths > 0) { + $itemcount = scalar(@paths); + } else { + $itemcount = 1; + } + if ($is_camtasia) { + $output .= $lt{'auto'}.'
'. + ''.$lt{'proa'}.' 
'. + '
'. + &Apache::lonhtmlcommon::start_pick_box(). + &Apache::lonhtmlcommon::row_title($lt{'fold'}). + ''."\n". + &Apache::lonhtmlcommon::row_closure(). + &Apache::lonhtmlcommon::row_title($lt{'movi'}). + ''."\n". + &Apache::lonhtmlcommon::row_closure(1). + &Apache::lonhtmlcommon::end_pick_box(). + '
'; + } + $output .= + ''. + ''. + "\n"; + if ($duplicates ne '') { + $output .= '

'. + &mt('Warning: decompression of the archive will overwrite the following items which already exist:').'
'. + &start_data_table(). + &start_data_table_header_row(). + ''.&mt('Overwrite?').''. + ''.&mt('Name').''. + ''.&mt('Type').''. + ''.&mt('Size').''. + ''.&mt('Last modified').''. + &end_data_table_header_row(). + $duplicates. + &end_data_table(). + '

'; + } + $output .= ''."\n"; + if (ref($hiddenelements) eq 'HASH') { + foreach my $hidden (sort(keys(%{$hiddenelements}))) { + $output .= ''."\n"; + } + } + $output .= <<"END"; +
+ +
+$noextract +END + return $output; +} + +sub decompression_utility { + my ($program) = @_; + my @utilities = ('tar','gunzip','bunzip2','unzip'); + my $location; + if (grep(/^\Q$program\E$/,@utilities)) { + foreach my $dir ('/bin/','/usr/bin/','/usr/local/bin/','/sbin/', + '/usr/sbin/') { + if (-x $dir.$program) { + $location = $dir.$program; + last; + } + } + } + return $location; +} + +sub list_archive_contents { + my ($file,$pathsref) = @_; + my (@cmd,$output); + my $needsregexp; + if ($file =~ /\.zip$/) { + @cmd = (&decompression_utility('unzip'),"-l"); + $needsregexp = 1; + } elsif (($file =~ m/\.tar\.gz$/) || + ($file =~ /\.tgz$/)) { + @cmd = (&decompression_utility('tar'),"-ztf"); + } elsif ($file =~ /\.tar\.bz2$/) { + @cmd = (&decompression_utility('tar'),"-jtf"); + } elsif ($file =~ m|\.tar$|) { + @cmd = (&decompression_utility('tar'),"-tf"); + } + if (@cmd) { + undef($!); + undef($@); + if (open(my $fh,"-|", @cmd, $file)) { + while (my $line = <$fh>) { + $output .= $line; + chomp($line); + my $item; + if ($needsregexp) { + ($item) = ($line =~ /^\s*\d+\s+[\d\-]+\s+[\d:]+\s*(.+)$/); + } else { + $item = $line; + } + if ($item ne '') { + unless (grep(/^\Q$item\E$/,@{$pathsref})) { + push(@{$pathsref},$item); + } + } + } + close($fh); + } + } + return $output; +} + +sub decompress_uploaded_file { + my ($file,$dir) = @_; + &Apache::lonnet::appenv({'cgi.file' => $file}); + &Apache::lonnet::appenv({'cgi.dir' => $dir}); + my $result = &Apache::lonnet::ssi_body('/cgi-bin/decompress.pl'); + my ($handle) = ($env{'user.environment'} =~m{/([^/]+)\.id$}); + my $lonidsdir = $Apache::lonnet::perlvar{'lonIDsDir'}; + &Apache::lonnet::transfer_profile_to_env($lonidsdir,$handle,1); + my $decompressed = $env{'cgi.decompressed'}; + &Apache::lonnet::delenv('cgi.file'); + &Apache::lonnet::delenv('cgi.dir'); + &Apache::lonnet::delenv('cgi.decompressed'); + return ($decompressed,$result); +} + +sub process_decompression { + my ($docudom,$docuname,$file,$destination,$dir_root,$hiddenelem) = @_; + my ($dir,$error,$warning,$output); + if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/i) { + $error = &mt('Filename not a supported archive file type.'). + '
'.&mt('Filename should end with one of: [_1].', + '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz'); + } else { + my $docuhome = &Apache::lonnet::homeserver($docuname,$docudom); + if ($docuhome eq 'no_host') { + $error = &mt('Could not determine home server for course.'); + } else { + my @ids=&Apache::lonnet::current_machine_ids(); + my $currdir = "$dir_root/$destination"; + if (grep(/^\Q$docuhome\E$/,@ids)) { + $dir = &LONCAPA::propath($docudom,$docuname). + "$dir_root/$destination"; + } else { + $dir = $Apache::lonnet::perlvar{'lonDocRoot'}. + "$dir_root/$docudom/$docuname/$destination"; + unless (&Apache::lonnet::repcopy_userfile("$dir/$file") eq 'ok') { + $error = &mt('Archive file not found.'); + } + } + my (@to_overwrite,@to_skip); + if ($env{'form.archive_overwrite_total'} > 0) { + my $total = $env{'form.archive_overwrite_total'}; + for (my $i=0; $i<$total; $i++) { + if ($env{'form.archive_overwrite_'.$i} == 1) { + push(@to_overwrite,$env{'form.archive_overwrite_name_'.$i}); + } elsif ($env{'form.archive_overwrite_'.$i} == 0) { + push(@to_skip,$env{'form.archive_overwrite_name_'.$i}); + } + } + } + my $numskip = scalar(@to_skip); + if (($numskip > 0) && + ($numskip == $env{'form.archive_itemcount'})) { + $warning = &mt('All items in the archive file already exist, and no overwriting of existing files has been requested.'); + } elsif ($dir eq '') { + $error = &mt('Directory containing archive file unavailable.'); + } elsif (!$error) { + my ($decompressed,$display); + if ($numskip > 0) { + my $tempdir = time.'_'.$$.int(rand(10000)); + mkdir("$dir/$tempdir",0755); + system("mv $dir/$file $dir/$tempdir/$file"); + ($decompressed,$display) = + &decompress_uploaded_file($file,"$dir/$tempdir"); + foreach my $item (@to_skip) { + if (($item ne '') && ($item !~ /\.\./)) { + if (-f "$dir/$tempdir/$item") { + unlink("$dir/$tempdir/$item"); + } elsif (-d "$dir/$tempdir/$item") { + system("rm -rf $dir/$tempdir/$item"); + } + } + } + system("mv $dir/$tempdir/* $dir"); + rmdir("$dir/$tempdir"); + } else { + ($decompressed,$display) = + &decompress_uploaded_file($file,$dir); + } + if ($decompressed eq 'ok') { + $output = '

'. + &mt('Files extracted successfully from archive.'). + '

'."\n"; + my ($warning,$result,@contents); + my ($newdirlistref,$newlisterror) = + &Apache::lonnet::dirlist($currdir,$docudom, + $docuname,1); + my (%is_dir,%changes,@newitems); + my $dirptr = 16384; + if (ref($newdirlistref) eq 'ARRAY') { + foreach my $dir_line (@{$newdirlistref}) { + my ($item,undef,undef,$testdir)=split(/\&/,$dir_line,5); + unless (($item =~ /^\.+$/) || ($item eq $file) || + ((@to_skip > 0) && (grep(/^\Q$item\E$/,@to_skip)))) { + push(@newitems,$item); + if ($dirptr&$testdir) { + $is_dir{$item} = 1; + } + $changes{$item} = 1; + } + } + } + if (keys(%changes) > 0) { + foreach my $item (sort(@newitems)) { + if ($changes{$item}) { + push(@contents,$item); + } + } + } + if (@contents > 0) { + my $wantform; + unless ($env{'form.autoextract_camtasia'}) { + $wantform = 1; + } + my (%children,%parent,%dirorder,%titles); + my ($count,$datatable) = &get_extracted($docudom,$docuname, + $currdir,\%is_dir, + \%children,\%parent, + \@contents,\%dirorder, + \%titles,$wantform); + if ($datatable ne '') { + $output .= &archive_options_form('decompressed',$datatable, + $count,$hiddenelem); + my $startcount = 6; + $output .= &archive_javascript($startcount,$count, + \%titles,\%children); + } + if ($env{'form.autoextract_camtasia'}) { + my $version = $env{'form.autoextract_camtasia'}; + my %displayed; + my $total = 1; + $env{'form.archive_directory'} = []; + foreach my $i (sort { $a <=> $b } keys(%dirorder)) { + my $path = join('/',map { $titles{$_}; } @{$dirorder{$i}}); + $path =~ s{/$}{}; + my $item; + if ($path ne '') { + $item = "$path/$titles{$i}"; + } else { + $item = $titles{$i}; + } + $env{'form.archive_content_'.$i} = "$dir_root/$destination/$item"; + if ($item eq $contents[0]) { + push(@{$env{'form.archive_directory'}},$i); + $env{'form.archive_'.$i} = 'display'; + $env{'form.archive_title_'.$i} = $env{'form.camtasia_foldername'}; + $displayed{'folder'} = $i; + } elsif ((($item eq "$contents[0]/index.html") && ($version == 6)) || + (($item eq "$contents[0]/$contents[0]".'.html') && ($version == 8))) { + $env{'form.archive_'.$i} = 'display'; + $env{'form.archive_title_'.$i} = $env{'form.camtasia_moviename'}; + $displayed{'web'} = $i; + } else { + if ((($item eq "$contents[0]/media") && ($version == 6)) || + ((($item eq "$contents[0]/scripts") || ($item eq "$contents[0]/skins") || + ($item eq "$contents[0]/skins/express_show")) && ($version == 8))) { + push(@{$env{'form.archive_directory'}},$i); + } + $env{'form.archive_'.$i} = 'dependency'; + } + $total ++; + } + for (my $i=1; $i<$total; $i++) { + next if ($i == $displayed{'web'}); + next if ($i == $displayed{'folder'}); + $env{'form.archive_dependent_on_'.$i} = $displayed{'web'}; + } + $env{'form.phase'} = 'decompress_cleanup'; + $env{'form.archivedelete'} = 1; + $env{'form.archive_count'} = $total-1; + $output .= + &process_extracted_files('coursedocs',$docudom, + $docuname,$destination, + $dir_root,$hiddenelem); + } + } else { + $warning = &mt('No new items extracted from archive file.'); + } + } else { + $output = $display; + $error = &mt('An error occurred during extraction from the archive file.'); + } + } + } + } + if ($error) { + $output .= '

'.&mt('Not extracted.').'
'. + $error.'

'."\n"; + } + if ($warning) { + $output .= '

'.$warning.'

'."\n"; + } + return $output; +} + +sub get_extracted { + my ($docudom,$docuname,$currdir,$is_dir,$children,$parent,$contents,$dirorder, + $titles,$wantform) = @_; + my $count = 0; + my $depth = 0; + my $datatable; + my @hierarchy; + return unless ((ref($is_dir) eq 'HASH') && (ref($children) eq 'HASH') && + (ref($parent) eq 'HASH') && (ref($contents) eq 'ARRAY') && + (ref($dirorder) eq 'HASH') && (ref($titles) eq 'HASH')); + foreach my $item (@{$contents}) { + $count ++; + @{$dirorder->{$count}} = @hierarchy; + $titles->{$count} = $item; + &archive_hierarchy($depth,$count,$parent,$children); + if ($wantform) { + $datatable .= &archive_row($is_dir->{$item},$item, + $currdir,$depth,$count); + } + if ($is_dir->{$item}) { + $depth ++; + push(@hierarchy,$count); + $parent->{$depth} = $count; + $datatable .= + &recurse_extracted_archive("$currdir/$item",$docudom,$docuname, + \$depth,\$count,\@hierarchy,$dirorder, + $children,$parent,$titles,$wantform); + $depth --; + pop(@hierarchy); + } + } + return ($count,$datatable); +} + +sub recurse_extracted_archive { + my ($currdir,$docudom,$docuname,$depth,$count,$hierarchy,$dirorder, + $children,$parent,$titles,$wantform) = @_; + my $result=''; + unless ((ref($depth)) && (ref($count)) && (ref($hierarchy) eq 'ARRAY') && + (ref($children) eq 'HASH') && (ref($parent) eq 'HASH') && + (ref($dirorder) eq 'HASH')) { + return $result; + } + my $dirptr = 16384; + my ($newdirlistref,$newlisterror) = + &Apache::lonnet::dirlist($currdir,$docudom,$docuname,1); + if (ref($newdirlistref) eq 'ARRAY') { + foreach my $dir_line (@{$newdirlistref}) { + my ($item,undef,undef,$testdir)=split(/\&/,$dir_line,5); + unless ($item =~ /^\.+$/) { + $$count ++; + @{$dirorder->{$$count}} = @{$hierarchy}; + $titles->{$$count} = $item; + &archive_hierarchy($$depth,$$count,$parent,$children); + + my $is_dir; + if ($dirptr&$testdir) { + $is_dir = 1; + } + if ($wantform) { + $result .= &archive_row($is_dir,$item,$currdir,$$depth,$$count); + } + if ($is_dir) { + $$depth ++; + push(@{$hierarchy},$$count); + $parent->{$$depth} = $$count; + $result .= + &recurse_extracted_archive("$currdir/$item",$docudom, + $docuname,$depth,$count, + $hierarchy,$dirorder,$children, + $parent,$titles,$wantform); + $$depth --; + pop(@{$hierarchy}); + } + } + } + } + return $result; +} + +sub archive_hierarchy { + my ($depth,$count,$parent,$children) =@_; + if ((ref($parent) eq 'HASH') && (ref($children) eq 'HASH')) { + if (exists($parent->{$depth})) { + $children->{$parent->{$depth}} .= $count.':'; + } + } + return; +} + +sub archive_row { + my ($is_dir,$item,$currdir,$depth,$count) = @_; + my ($name) = ($item =~ m{([^/]+)$}); + my %choices = &Apache::lonlocal::texthash ( + 'display' => 'Add as file', + 'dependency' => 'Include as dependency', + 'discard' => 'Discard', + ); + if ($is_dir) { + $choices{'display'} = &mt('Add as folder'); + } + my $output = &start_data_table_row().''.$count.''."\n"; + my $offset = 0; + foreach my $action ('display','dependency','discard') { + $offset ++; + if ($action ne 'display') { + $offset ++; + } + $output .= ''. + ''; + if ($action eq 'dependency') { + $output .= ''; + } elsif ($action eq 'display') { + $output .= ''; + } + $output .= ''; + } + $output .= '&').'" />'.(' ' x 2); + for (my $i=0; $i<$depth; $i++) { + $output .= ('' x2)."\n"; + } + if ($is_dir) { + $output .= ' '."\n". + ''."\n"; + } else { + $output .= ''."\n"; + } + $output .= ' '.$name.''."\n". + &end_data_table_row(); + return $output; +} + +sub archive_options_form { + my ($form,$display,$count,$hiddenelem) = @_; + my %lt = &Apache::lonlocal::texthash( + perm => 'Permanently remove archive file?', + hows => 'How should each extracted item be incorporated in the course?', + cont => 'Content actions for all', + addf => 'Add as folder/file', + incd => 'Include as dependency for a displayed file', + disc => 'Discard', + no => 'No', + yes => 'Yes', + save => 'Save', + ); + my $output = <<"END"; +
+

$lt{'perm'}  + +  + + +

+ +
$lt{'hows'} +
+
+ $lt{'cont'} + +    +    +
+
+END + return $output. + &start_data_table()."\n". + $display."\n". + &end_data_table()."\n". + ''. + $hiddenelem. + '
'. + '
'; +} + +sub archive_javascript { + my ($startcount,$numitems,$titles,$children) = @_; + return unless ((ref($titles) eq 'HASH') && (ref($children) eq 'HASH')); + my $maintitle = $env{'form.comment'}; + my $scripttag = < +// 0) { + var startelement = $startcount + ((count-1) * 7); + for (var j=1; j<6; j++) { + if ((j != 2) && (j != 4)) { + var item = startelement + j; + if (form.elements[item].type == 'radio') { + if (form.elements[item].checked) { + containerCheck(form,count,j); + break; + } + } + } + } + } +} + +numitems = $numitems +var titles = new Array(numitems); +var parents = new Array(numitems); +for (var i=0; i $b } (keys(%{$children}))) { + my @contents = split(/:/,$children->{$container}); + for (my $i=0; $i<@contents; $i ++) { + $scripttag .= 'parents['.$container.']['.$i.'] = '.$contents[$i]."\n"; + } + } + + foreach my $key (sort { $a <=> $b } (keys(%{$titles}))) { + $scripttag .= "titles[$key] = '".$titles->{$key}."';\n"; + } + + $scripttag .= <