--- loncom/interface/loncommon.pm 2010/08/07 19:23:50 1.972
+++ loncom/interface/loncommon.pm 2015/03/09 19:09:34 1.1209
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.972 2010/08/07 19:23:50 raeburn Exp $
+# $Id: loncommon.pm,v 1.1209 2015/03/09 19:09:34 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -67,9 +67,17 @@ 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 DateTime::TimeZone;
use DateTime::Locale::Catalog;
+use Text::Aspell;
+use Authen::Captcha;
+use Captcha::reCAPTCHA;
+use Crypt::DES;
+use DynaLoader; # for Crypt::DES version
# ---------------------------------------------- Designs
use vars qw(%defaultdesign);
@@ -154,6 +162,9 @@ sub ssi_with_retries {
# ----------------------------------------------- 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 %scprtag;
my %fe; my %fd; my %fm;
@@ -186,11 +197,16 @@ BEGIN {
while (my $line = <$fh>) {
next if ($line=~/^\#/);
chomp($line);
- my ($key,$two,$country,$three,$enc,$val,$sup)=(split(/\t/,$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);
}
@@ -409,7 +425,7 @@ sub studentbrowser_javascript {
+ENDRESBRW
+}
+
sub selectstudent_link {
- my ($form,$unameele,$udomele,$courseadvonly)=@_;
- my $callargs = "'".$form."','".$unameele."','".$udomele."'";
+ 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";
}
@@ -452,7 +491,7 @@ sub selectstudent_link {
&mt('Select User').'';
}
if ($env{'request.role'}=~/^(au|dc|su)/) {
- $callargs .= ",1";
+ $callargs .= ",'',1";
return ''.
''.
&mt('Select User').' ';
@@ -460,6 +499,19 @@ sub selectstudent_link {
return '';
}
+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 authorbrowser_javascript {
return <<"ENDAUTHORBRW";
+
+ENDJS
+
+}
+
sub userbrowser_javascript {
my $id_functions = &javascript_index_functions();
return <<"ENDUSERBRW";
@@ -658,7 +757,7 @@ ENDUSERBRW
}
sub setsec_javascript {
- my ($sec_element,$formname,$role_element) = @_;
+ my ($sec_element,$formname,$role_element,$credits_element) = @_;
my (@courserolenames,@communityrolenames,$rolestr,$courserolestr,
$communityrolestr);
if ($role_element ne '') {
@@ -753,6 +852,14 @@ function setRole(crstype) {
}
|;
}
+ if ($credits_element) {
+ $setsections .= qq|
+function setCredits(defaultcredits) {
+ document.$formname.$credits_element.value = defaultcredits;
+ return;
+}
+|;
+ }
return $setsections;
}
@@ -766,6 +873,9 @@ sub selectcourse_link {
} elsif ($selecttype eq 'Course/Community') {
$linktext = &mt('Select Course/Community');
$type = '';
+ } elsif ($selecttype eq 'Select') {
+ $linktext = &mt('Select');
+ $type = '';
}
return ''
."
END
# output the initial values for the selection lists
- $result .= "\n";
+ $result .= "\n";
my @order = sort(keys(%{$hashref}));
if (ref($menuorder) eq 'ARRAY') {
@order = @{$menuorder};
@@ -1053,7 +1203,11 @@ END
$result .= " \n";
my %select2 = %{$hashref->{$firstdefault}->{'select2'}};
$result .= $middletext;
- $result .= "\n";
+ $result .= "{$firstdefault}->{'default'};
my @secondorder = sort(keys(%select2));
@@ -1072,7 +1226,7 @@ END
=pod
-=item * &help_open_topic($topic,$text,$stayOnPage,$width,$height)
+=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
@@ -1090,15 +1244,19 @@ a new window using Javascript. (Default
$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.
+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) = @_;
+ my ($topic, $text, $stayOnPage, $width, $height, $imgid) = @_;
$text = "" if (not defined $text);
$stayOnPage = 0 if (not defined $stayOnPage);
- $width = 350 if (not defined $width);
+ $width = 500 if (not defined $width);
$height = 400 if (not defined $height);
my $filename = $topic;
$filename =~ s/ /_/g;
@@ -1109,7 +1267,9 @@ sub help_open_topic {
$topic=~s/\W/\_/g;
if (!$stayOnPage) {
- $link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
+ $link = "javascript:openMyModal('/adm/help/${filename}.hlp',$width,$height,'yes');";
+ } elsif ($stayOnPage eq 'popup') {
+ $link = "javascript:void(open('/adm/help/${filename}.hlp', 'Help_for_$topic', 'menubar=0,toolbar=1,scrollbars=1,width=$width,height=$height,resizable=yes'))";
} else {
$link = "/adm/help/${filename}.hlp";
}
@@ -1124,10 +1284,13 @@ sub help_open_topic {
# (Always) Add the graphic
my $title = &mt('Online Help');
my $helpicon=&lonhttpdurl("/adm/help/help.png");
+ if ($imgid ne '') {
+ $imgid = ' id="'.$imgid.'"';
+ }
$template.=' '
.' ';
if ($text ne "") {
$template.=' ';
@@ -1139,27 +1302,24 @@ sub help_open_topic {
# This is a quicky function for Latex cheatsheet editing, since it
# appears in at least four places
sub helpLatexCheatsheet {
- my ($topic,$text,$not_author) = @_;
+ my ($topic,$text,$not_author,$stayOnPage) = @_;
my $out;
my $addOther = '';
if ($topic) {
- $addOther = ''.&Apache::loncommon::help_open_topic($topic,&mt($text),
- undef, undef, 600).
- ' ';
+ $addOther = ''.&help_open_topic($topic,&mt($text),$stayOnPage, undef, 600).' ';
}
$out = '' # Start cheatsheet
.$addOther
.''
- .&Apache::loncommon::help_open_topic('Greek_Symbols',&mt('Greek Symbols'),
- undef,undef,600)
+ .&help_open_topic('Greek_Symbols',&mt('Greek Symbols'),$stayOnPage,undef,600)
.' '
- .&Apache::loncommon::help_open_topic('Other_Symbols',&mt('Other Symbols'),
- undef,undef,600)
+ .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600)
.' ';
unless ($not_author) {
- $out .= ' '
- .&Apache::loncommon::help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),
- undef,undef,600)
+ $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
@@ -1225,32 +1385,38 @@ sub top_nav_help {
$text = &mt($text);
my $stay_on_page = 1;
- my $link = ($stay_on_page) ? "javascript:helpMenu('display')"
- : "javascript:helpMenu('open')";
- my $banner_link = &update_help_link(undef,undef,undef,undef,$stay_on_page);
-
+ my ($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');
-
- return <<"END";
+ if ($link) {
+ return <<"END";
$banner_link
- $text
+$text
END
+ } else {
+ return ' '.$text.' ';
+ }
}
sub help_menu_js {
- my ($text) = @_;
+ my ($httphost) = @_;
my $stayOnPage = 1;
my $width = 620;
my $height = 600;
my $helptopic=&general_help();
- my $details_link = '/adm/help/'.$helptopic.'.hlp';
+ 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',
+ 'border' => '0',
'rows' => "110,*",},});
my $end_page =
&Apache::loncommon::end_page({'frameset' => 1,
@@ -1280,9 +1446,10 @@ function helpMenu(target) {
return;
}
function writeHelp(caller) {
- caller.document.writeln('$start_page $end_page')
- caller.document.close()
- caller.focus()
+ caller.document.writeln('$start_page\\n \\n');
+ caller.document.writeln(' \\n$end_page');
+ caller.document.close();
+ caller.focus();
}
// END LON-CAPA Internal -->
// ]]>
@@ -1590,12 +1757,245 @@ RESIZE
}
+sub colorfuleditor_js {
+ return <<"COLORFULEDIT"
+
+COLORFULEDIT
+}
+
+sub xmleditor_js {
+ return <
+
+XMLEDIT
+}
+
+sub insert_folding_button {
+ my $curDepth = $Apache::lonxml::curdepth;
+ my $lastresource = $env{'request.ambiguous'};
+
+ return " ";
+}
+
=pod
=head1 Excel and CSV file utility routines
-=over 4
-
=cut
###############################################################
@@ -1603,6 +2003,8 @@ RESIZE
=pod
+=over 4
+
=item * &csv_translate($text)
Translate $text to allow it to be output as a 'comma separated values'
@@ -1654,6 +2056,7 @@ Inputs: $workbook
Returns: $format, a hash reference.
+
=cut
###############################################################
@@ -1715,7 +2118,7 @@ sub create_workbook {
return (undef);
}
#
- $workbook->set_tempdir('/home/httpd/perl/tmp');
+ $workbook->set_tempdir(LONCAPA::tempdir());
#
my $format = &Apache::loncommon::define_excel_formats($workbook);
return ($workbook,$filename,$format);
@@ -1886,19 +2289,112 @@ sub select_form {
# For display filters
sub display_filter {
+ my ($context) = @_;
if (!$env{'form.show'}) { $env{'form.show'}=10; }
if (!$env{'form.displayfilter'}) { $env{'form.displayfilter'}='currentfolder'; }
- return ''.&mt('Records [_1]',
+ 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 =
+ ' '.
+ ' '.$includetypestext.' '.
+ ' ';
+ $secondid = 'includetypes';
+ $thirdid = 'includetypestext';
+ }
+ my $onchange = "javascript:toggleHistoryOptions(this,'containingphrase','$context',
+ '$secondid','$thirdid')";
+ return ''.&mt('Records: [_1]',
&Apache::lonmeta::selectbox('show',$env{'form.show'},undef,
(&mt('all'),10,20,50,100,1000,10000))).
' '.
- &mt('Filter [_1]',
+ &mt('Filter: [_1]',
&select_form($env{'form.displayfilter'},
'displayfilter',
{'currentfolder' => 'Current folder/page',
'containing' => 'Containing phrase',
- 'none' => 'None'})).
- ' ';
+ '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 {
@@ -1942,7 +2438,7 @@ sub select_level_form {
=pod
-=item * &select_dom_form($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms)
+=item * &select_dom_form($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms)
Returns a string containing a form to
allow a user to select the domain to preform an operation in.
@@ -1955,25 +2451,31 @@ If the $showdomdesc flag is set, the dom
The optional $onchange argument specifies what should occur if the domain selector is changed, e.g., 'this.form.submit()' if the form is to be automatically submitted.
-The optional $incdoms is a reference to an array of domains which will be the only available options.
+The optional $incdoms is a reference to an array of domains which will be the only available options.
+
+The optional $excdoms is a reference to an array of domains which will be excluded from the available options.
=cut
#-------------------------------------------
sub select_dom_form {
- my ($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms) = @_;
+ my ($defdom,$name,$includeempty,$showdomdesc,$onchange,$incdoms,$excdoms) = @_;
if ($onchange) {
$onchange = ' onchange="'.$onchange.'"';
}
- my @domains;
+ my (@domains,%exclude);
if (ref($incdoms) eq 'ARRAY') {
@domains = sort {lc($a) cmp lc($b)} (@{$incdoms});
} else {
@domains = sort {lc($a) cmp lc($b)} (&Apache::lonnet::all_domains());
}
if ($includeempty) { @domains=('',@domains); }
+ if (ref($excdoms) eq 'ARRAY') {
+ map { $exclude{$_} = 1; } @{$excdoms};
+ }
my $selectdomain = "\n";
foreach my $dom (@domains) {
+ next if ($exclude{$dom});
$selectdomain.="'.$dom;
if ($showdomdesc) {
@@ -2094,6 +2596,12 @@ Outputs:
=item * $clientos
+=item * $clientmobile
+
+=item * $clientinfo
+
+=item * $clientosversion
+
=back
=back
@@ -2112,8 +2620,10 @@ sub decode_user_agent {
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]);
+ my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\%/,$browsertype[$i]);
if (($httpbrowser=~/$match/i) && ($httpbrowser!~/$notmatch/i)) {
$clientbrowser=$bname;
$httpbrowser=~/$vreg/i;
@@ -2123,6 +2633,7 @@ sub decode_user_agent {
}
}
my $clientos='unknown';
+ my $clientinfo;
if (($httpbrowser=~/linux/i) ||
($httpbrowser=~/unix/i) ||
($httpbrowser=~/ux/i) ||
@@ -2132,10 +2643,24 @@ sub decode_user_agent {
if ($httpbrowser=~/next/i) { $clientos='next'; }
if (($httpbrowser=~/mac/i) ||
($httpbrowser=~/powerpc/i)) { $clientos='mac'; }
- if ($httpbrowser=~/win/i) { $clientos='win'; }
+ 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,);
+ $clientunicode,$clientos,$clientmobile,$clientinfo,
+ $clientosversion);
}
###############################################################
@@ -2267,12 +2792,16 @@ function changed_text(choice,currentform
}
function set_auth_radio_buttons(newvalue,currentform) {
+ var numauthchoices = currentform.login.length;
+ if (typeof numauthchoices == "undefined") {
+ return;
+ }
var i=0;
- while (i < currentform.login.length) {
+ while (i < numauthchoices) {
if (currentform.login[i].value == newvalue) { break; }
i++;
}
- if (i == currentform.login.length) {
+ if (i == numauthchoices) {
return;
}
current.radiovalue = newvalue;
@@ -2283,7 +2812,7 @@ END
return $result;
}
-sub authform_authorwarning{
+sub authform_authorwarning {
my $result='';
$result=''.
&mt('As a general rule, only authors or co-authors should be '.
@@ -2292,16 +2821,16 @@ sub authform_authorwarning{
return $result;
}
-sub authform_nochange{
+sub authform_nochange {
my %in = (
formname => 'document.cu',
kerb_def_dom => 'MSU.EDU',
@_,
);
- my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+ my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
my $result;
- if (keys(%can_assign) == 0) {
- $result = &mt('Under you current role you are not permitted to change login settings for this user');
+ if (!$authnum) {
+ $result = &mt('Under your current role you are not permitted to change login settings for this user');
} else {
$result = ''.&mt('[_1] Do not change login data',
' ';
+ $authtype = ' ';
}
}
}
@@ -2383,9 +2912,9 @@ sub authform_kerberos {
$krbcheck.' />';
}
if (($can_assign{'krb4'} && $can_assign{'krb5'}) ||
- ($can_assign{'krb4'} && !$can_assign{'krb5'} &&
+ ($can_assign{'krb4'} && !$can_assign{'krb5'} &&
$in{'curr_authtype'} eq 'krb5') ||
- (!$can_assign{'krb4'} && $can_assign{'krb5'} &&
+ (!$can_assign{'krb4'} && $can_assign{'krb5'} &&
$in{'curr_authtype'} eq 'krb4')) {
$result .= &mt
('[_1] Kerberos authenticated with domain [_2] '.
@@ -2421,14 +2950,14 @@ sub authform_kerberos {
return $result;
}
-sub authform_internal{
+sub authform_internal {
my %in = (
formname => 'document.cu',
kerb_def_dom => 'MSU.EDU',
@_,
);
my ($intcheck,$intarg,$result,$authtype,$autharg,$jscall);
- my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+ my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
if (defined($in{'curr_authtype'})) {
if ($in{'curr_authtype'} eq 'int') {
if ($can_assign{'int'}) {
@@ -2457,7 +2986,7 @@ sub authform_internal{
if (defined($in{'mode'})) {
if ($in{'mode'} eq 'modifycourse') {
if ($authnum == 1) {
- $authtype = ' ';
+ $authtype = ' ';
}
}
}
@@ -2476,14 +3005,14 @@ sub authform_internal{
return $result;
}
-sub authform_local{
+sub authform_local {
my %in = (
formname => 'document.cu',
kerb_def_dom => 'MSU.EDU',
@_,
);
my ($loccheck,$locarg,$result,$authtype,$autharg,$jscall);
- my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+ my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
if (defined($in{'curr_authtype'})) {
if ($in{'curr_authtype'} eq 'loc') {
if ($can_assign{'loc'}) {
@@ -2512,7 +3041,7 @@ sub authform_local{
if (defined($in{'mode'})) {
if ($in{'mode'} eq 'modifycourse') {
if ($authnum == 1) {
- $authtype = ' ';
+ $authtype = ' ';
}
}
}
@@ -2530,14 +3059,14 @@ sub authform_local{
return $result;
}
-sub authform_filesystem{
+sub authform_filesystem {
my %in = (
formname => 'document.cu',
kerb_def_dom => 'MSU.EDU',
@_,
);
my ($fsyscheck,$result,$authtype,$autharg,$jscall);
- my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
+ my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'});
if (defined($in{'curr_authtype'})) {
if ($in{'curr_authtype'} eq 'fsys') {
if ($can_assign{'fsys'}) {
@@ -2563,7 +3092,7 @@ sub authform_filesystem{
if (defined($in{'mode'})) {
if ($in{'mode'} eq 'modifycourse') {
if ($authnum == 1) {
- $authtype = ' ';
+ $authtype = ' ';
}
}
}
@@ -2758,6 +3287,7 @@ database which holds them.
Uses global $thesaurus_db_file.
+
=cut
###############################################################
@@ -2793,13 +3323,73 @@ sub get_related_words {
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
@@ -2990,7 +3580,7 @@ sub screenname {
# ------------------------------------------------------------- Confirm Wrapper
=pod
-=item confirmwrapper
+=item * &confirmwrapper($message)
Wrap messages about completion of operation in box
@@ -3029,12 +3619,12 @@ sub noteswrapper {
# ------------------------------------------------------------- Aboutme Wrapper
sub aboutmewrapper {
- my ($link,$username,$domain,$target)=@_;
+ my ($link,$username,$domain,$target,$class)=@_;
if (!defined($username) && !defined($domain)) {
return;
}
- return ''.$link.' ';
+ return ''.$link.' ';
}
# ------------------------------------------------------------ Syllabus Wrapper
@@ -3135,11 +3725,29 @@ sub languagedescription {
($supported_language{$code}?' ('.&mt('interface available').')':'');
}
+=pod
+
+=item * &plainlanguagedescription
+
+Returns both the plain language description (e.g. 'Creoles and Pidgins, English-based (Other)')
+and the language character encoding (e.g. ISO) separated by a ' - ' string.
+
+=cut
+
sub plainlanguagedescription {
my $code=shift;
return $language{$code};
}
+=pod
+
+=item * &supportedlanguagecode
+
+Returns the supported language code (e.g. sptutf maps to pt) given a language
+code.
+
+=cut
+
sub supportedlanguagecode {
my $code=shift;
return $supported_language{$code};
@@ -3147,6 +3755,35 @@ sub supportedlanguagecode {
=pod
+=item * &latexlanguage()
+
+Given a language key code returns the correspondnig language to use
+to select the correct hyphenation on LaTeX printouts. This is undef if there
+is no supported hyphenation for the language code.
+
+=cut
+
+sub latexlanguage {
+ my $code = shift;
+ return $latex_language{$code};
+}
+
+=pod
+
+=item * &latexhyphenation()
+
+Same as above but what's supplied is the language as it might be stored
+in the metadata.
+
+=cut
+
+sub latexhyphenation {
+ my $key = shift;
+ return $latex_language_bykey{$key};
+}
+
+=pod
+
=item * ©rightids()
returns list of all copyrights
@@ -3359,7 +3996,7 @@ sub user_lang {
=over 4
=item * &get_previous_attempt($symb, $username, $domain, $course,
- $getattempt, $regexp, $gradesub)
+ $getattempt, $regexp, $gradesub, $usec, $identifier)
Return string with previous attempt on problem. Arguments:
@@ -3381,6 +4018,11 @@ Return string with previous attempt on p
=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.
@@ -3388,7 +4030,7 @@ The output string is a table containing
=cut
sub get_previous_attempt {
- my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_;
+ my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub,$usec,$identifier)=@_;
my $prevattempts='';
no strict 'refs';
if ($symb) {
@@ -3404,22 +4046,36 @@ sub get_previous_attempt {
}
$prevattempts=&start_data_table().&start_data_table_header_row();
$prevattempts.=''.&mt('History').' ';
- my %typeparts;
+ 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.=''.&mt('Part ').join('.',@parts).' '.$data.' ';
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;
+ }
}
- delete($lasthash{$key});
- } else {
- $prevattempts.=''.&mt('Part ').join('.',@parts).' '.$data.' ';
- }
+ 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 {
if ($#parts == 0) {
$prevattempts.=''.$parts[0].' ';
@@ -3429,26 +4085,64 @@ sub get_previous_attempt {
}
}
$prevattempts.=&end_data_table_header_row();
- my %lasthidden;
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;
+ my (@hidden,@unsolved);
if (%typeparts) {
foreach my $id (keys(%typeparts)) {
- if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
+ if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') ||
+ ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) {
push(@hidden,$id);
- $lasthidden{$id} = 1;
- } elsif ($lasthidden{$id}) {
- if (exists($returnhash{$version.':'.$id.'.award'})) {
- delete($lasthidden{$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().
- ''.&mt('Transaction [_1]',$version).' ';
+ ''.&mt('Transaction [_1]',$version);
+ if (@unsolved) {
+ $prevattempts .= ''.
+ ' '.
+ &mt('Hide').' ';
+ }
+ $prevattempts .= ' ';
if (@hidden) {
foreach my $key (sort(keys(%lasthash))) {
+ next if ($key =~ /\.foilorder$/);
my $hide;
foreach my $id (@hidden) {
if ($key =~ /^\Q$id\E/) {
@@ -3477,6 +4171,7 @@ sub get_previous_attempt {
}
} else {
foreach my $key (sort(keys(%lasthash))) {
+ next if ($key =~ /\.foilorder$/);
my $value = &format_previous_attempt_value($key,
$returnhash{$version.':'.$key});
$prevattempts.=''.$value.' ';
@@ -3488,6 +4183,7 @@ sub get_previous_attempt {
my @currhidden = keys(%lasthidden);
$prevattempts.=&start_data_table_row().''.&mt('Current').' ';
foreach my $key (sort(keys(%lasthash))) {
+ next if ($key =~ /\.foilorder$/);
if (%typeparts) {
my $hidden;
foreach my $id (@currhidden) {
@@ -3503,7 +4199,7 @@ sub get_previous_attempt {
if ($key =~/$regexp$/ && (defined &$gradesub)) {
$value = &$gradesub($value);
}
- $prevattempts.=''.$value.' ';
+ $prevattempts.=''. $value.' ';
} else {
$prevattempts.=' ';
}
@@ -3519,7 +4215,7 @@ sub get_previous_attempt {
if ($key =~/$regexp$/ && (defined &$gradesub)) {
$value = &$gradesub($value);
}
- $prevattempts.=''.$value.' ';
+ $prevattempts.=''.$value.' ';
}
}
$prevattempts.= &end_data_table_row().&end_data_table();
@@ -3539,12 +4235,37 @@ sub get_previous_attempt {
sub format_previous_attempt_value {
my ($key,$value) = @_;
- if ($key =~ /timestamp/) {
- $value = &Apache::lonlocal::locallocaltime($value);
+ if (($key =~ /timestamp/) || ($key=~/duedate/)) {
+ $value = &Apache::lonlocal::locallocaltime($value);
} elsif (ref($value) eq 'ARRAY') {
- $value = '('.join(', ', @{ $value }).')';
+ $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 = &unescape($value);
+ $value = &HTML::Entities::encode(&unescape($value), '"<>&');
}
return $value;
}
@@ -3846,18 +4567,25 @@ sub findallcourses {
if ($tstart) {
next if ($tstart > $now);
}
- my ($cdom,$cnum,$sec,$cnumpart,$secpart,$role,$realsec);
+ my ($cdom,$cnum,$sec,$cnumpart,$secpart,$role);
(undef,$cdom,$cnumpart,$secpart) = split(/\//,$entry);
+ my $value = $trole.'/'.$cdom.'/';
if ($secpart eq '') {
($cnum,$role) = split(/_/,$cnumpart);
$sec = 'none';
- $realsec = '';
+ $value .= $cnum.'/';
} else {
$cnum = $cnumpart;
($sec,$role) = split(/_/,$secpart);
- $realsec = $sec;
+ $value .= $cnum.'/'.$sec;
+ }
+ if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') {
+ unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) {
+ push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value);
+ }
+ } else {
+ @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value);
}
- $courses{$cdom.'_'.$cnum}{$sec} = $trole.'/'.$cdom.'/'.$cnum.'/'.$realsec;
}
} else {
foreach my $key (keys(%env)) {
@@ -3875,11 +4603,19 @@ sub findallcourses {
if ($now>$endtime) { $active=0; }
}
if ($active) {
+ my $value = $role.'/'.$cdom.'/'.$cnum.'/';
if ($sec eq '') {
$sec = 'none';
+ } else {
+ $value .= $sec;
+ }
+ if (ref($courses{$cdom.'_'.$cnum}{$sec}) eq 'ARRAY') {
+ unless (grep(/^\Q$value\E$/,@{$courses{$cdom.'_'.$cnum}{$sec}})) {
+ push(@{$courses{$cdom.'_'.$cnum}{$sec}},$value);
+ }
+ } else {
+ @{$courses{$cdom.'_'.$cnum}{$sec}} = ($value);
}
- $courses{$cdom.'_'.$cnum}{$sec} =
- $role.'/'.$cdom.'/'.$cnum.'/'.$sec;
}
}
}
@@ -3890,32 +4626,31 @@ sub findallcourses {
###############################################
sub blockcheck {
- my ($setters,$activity,$uname,$udom) = @_;
+ my ($setters,$activity,$uname,$udom,$url,$is_course) = @_;
- if (!defined($udom)) {
+ 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'};
- }
- if (!defined($uname)) {
$uname = $env{'user.name'};
}
- # If uname and udom are for a course, check for blocks in the course.
-
- if (&Apache::lonnet::is_course($udom,$uname)) {
- my %records = &Apache::lonnet::dump('comm_block',$udom,$uname);
- my ($startblock,$endblock)=&get_blocks($setters,$activity,$udom,$uname);
- return ($startblock,$endblock);
- }
-
my $startblock = 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') && ($env{'request.course.id'})) {
+ $activity eq 'groups' || $activity eq 'printout') &&
+ ($env{'request.course.id'})) {
foreach my $key (keys(%live_courses)) {
if ($key ne $env{'request.course.id'}) {
delete($live_courses{$key});
@@ -3972,34 +4707,38 @@ sub blockcheck {
if ($otheruser) {
# Resource belongs to user other than current user.
# Assemble privs for that user, and check for 'evb' priv.
- my ($trole,$tdom,$tnum,$tsec);
- my $entry = $live_courses{$course}{$sec};
- if ($entry =~ /^cr/) {
- ($trole,$tdom,$tnum,$tsec) =
- ($entry =~ m|^(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_username)/?(\w*)$|);
- } else {
- ($trole,$tdom,$tnum,$tsec) = split(/\//,$entry);
- }
- my ($spec,$area,$trest,%allroles,%userroles);
- $area = '/'.$tdom.'/'.$tnum;
- $trest = $tnum;
- if ($tsec ne '') {
- $area .= '/'.$tsec;
- $trest .= '/'.$tsec;
- }
- $spec = $trole.'.'.$area;
- if ($trole =~ /^cr/) {
- &Apache::lonnet::custom_roleprivs(\%allroles,$trole,
- $tdom,$spec,$trest,$area);
- } else {
- &Apache::lonnet::standard_roleprivs(\%allroles,$trole,
- $tdom,$spec,$trest,$area);
- }
- my ($author,$adv) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
- if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) {
- if ($1) {
- $no_userblock = 1;
- last;
+ my (%allroles,%userroles);
+ if (ref($live_courses{$course}{$sec}) eq 'ARRAY') {
+ foreach my $entry (@{$live_courses{$course}{$sec}}) {
+ my ($trole,$tdom,$tnum,$tsec);
+ if ($entry =~ /^cr/) {
+ ($trole,$tdom,$tnum,$tsec) =
+ ($entry =~ m|^(cr/$match_domain/$match_username/\w+)\./($match_domain)/($match_username)/?(\w*)$|);
+ } else {
+ ($trole,$tdom,$tnum,$tsec) = split(/\//,$entry);
+ }
+ my ($spec,$area,$trest);
+ $area = '/'.$tdom.'/'.$tnum;
+ $trest = $tnum;
+ if ($tsec ne '') {
+ $area .= '/'.$tsec;
+ $trest .= '/'.$tsec;
+ }
+ $spec = $trole.'.'.$area;
+ if ($trole =~ /^cr/) {
+ &Apache::lonnet::custom_roleprivs(\%allroles,$trole,
+ $tdom,$spec,$trest,$area);
+ } else {
+ &Apache::lonnet::standard_roleprivs(\%allroles,$trole,
+ $tdom,$spec,$trest,$area);
+ }
+ }
+ my ($author,$adv) = &Apache::lonnet::set_userprivs(\%userroles,\%allroles);
+ if ($userroles{'user.priv.'.$checkrole} =~ /evb\&([^\:]*)/) {
+ if ($1) {
+ $no_userblock = 1;
+ last;
+ }
}
}
} else {
@@ -4019,46 +4758,139 @@ sub blockcheck {
# Retrieve blocking times and identity of locker for course
# of specified user, unless user has 'evb' privilege.
- my ($start,$end)=&get_blocks($setters,$activity,$cdom,$cnum);
+ my ($start,$end,$trigger) =
+ &get_blocks($setters,$activity,$cdom,$cnum,$url);
if (($start != 0) &&
(($startblock == 0) || ($startblock > $start))) {
$startblock = $start;
+ if ($trigger ne '') {
+ $triggerblock = $trigger;
+ }
}
if (($end != 0) &&
(($endblock == 0) || ($endblock < $end))) {
$endblock = $end;
+ if ($trigger ne '') {
+ $triggerblock = $trigger;
+ }
}
}
- return ($startblock,$endblock);
+ return ($startblock,$endblock,$triggerblock);
}
sub get_blocks {
- my ($setters,$activity,$cdom,$cnum) = @_;
+ my ($setters,$activity,$cdom,$cnum,$url) = @_;
my $startblock = 0;
my $endblock = 0;
+ my $triggerblock = '';
my $course = $cdom.'_'.$cnum;
$setters->{$course} = {};
$setters->{$course}{'staff'} = [];
$setters->{$course}{'times'} = [];
- my %records = &Apache::lonnet::dump('comm_block',$cdom,$cnum);
- foreach my $record (keys(%records)) {
- my ($start,$end) = ($record =~ m/^(\d+)____(\d+)$/);
- if ($start <= time && $end >= time) {
- my ($staff_name,$staff_dom,$title,$blocks) =
- &parse_block_record($records{$record});
- if ($blocks->{$activity} eq 'on') {
- push(@{$$setters{$course}{'staff'}},[$staff_name,$staff_dom]);
- push(@{$$setters{$course}{'times'}}, [$start,$end]);
- if ( ($startblock == 0) || ($startblock > $start) ) {
- $startblock = $start;
+ $setters->{$course}{'triggers'} = [];
+ my (@blockers,%triggered);
+ my $now = time;
+ my %commblocks = &Apache::lonnet::get_comm_blocks($cdom,$cnum);
+ if ($activity eq 'docs') {
+ @blockers = &Apache::lonnet::has_comm_blocking('bre',undef,$url,\%commblocks);
+ foreach my $block (@blockers) {
+ if ($block =~ /^firstaccess____(.+)$/) {
+ my $item = $1;
+ my $type = 'map';
+ my $timersymb = $item;
+ if ($item eq 'course') {
+ $type = 'course';
+ } elsif ($item =~ /___\d+___/) {
+ $type = 'resource';
+ } else {
+ $timersymb = &Apache::lonnet::symbread($item);
+ }
+ my $start = $env{'course.'.$cdom.'_'.$cnum.'.firstaccess.'.$timersymb};
+ my $end = $start + $env{'course.'.$cdom.'_'.$cnum.'.timerinterval.'.$timersymb};
+ $triggered{$block} = {
+ start => $start,
+ end => $end,
+ type => $type,
+ };
+ }
+ }
+ } else {
+ foreach my $block (keys(%commblocks)) {
+ if ($block =~ m/^(\d+)____(\d+)$/) {
+ my ($start,$end) = ($1,$2);
+ if ($start <= time && $end >= time) {
+ if (ref($commblocks{$block}) eq 'HASH') {
+ if (ref($commblocks{$block}{'blocks'}) eq 'HASH') {
+ if ($commblocks{$block}{'blocks'}{$activity} eq 'on') {
+ unless(grep(/^\Q$block\E$/,@blockers)) {
+ push(@blockers,$block);
+ }
+ }
+ }
+ }
}
- if ( ($endblock == 0) || ($endblock < $end) ) {
- $endblock = $end;
+ } 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)) {
+ unless (grep(/^\Q$block\E$/,@blockers)) {
+ push(@blockers,$block);
+ $triggered{$block} = {
+ start => $start,
+ end => $end,
+ type => $type,
+ };
+ }
+ }
}
}
}
}
- return ($startblock,$endblock);
+ 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 {
@@ -4082,39 +4914,50 @@ sub parse_block_record {
}
sub blocking_status {
- my ($activity,$uname,$udom) = @_;
- my %setters;
-
- # check for active blocking
- my ($startblock,$endblock)=&blockcheck(\%setters,$activity,$uname,$udom);
-
- my $blocked = $startblock && $endblock ? 1 : 0;
+ my ($activity,$uname,$udom,$url,$is_course) = @_;
+ my %setters;
- # caller just wants to know whether a block is active
- if (!wantarray) { return $blocked; }
-
- # build a link to a popup window containing the details
- my $querystring = "?activity=$activity";
- # $uname and $udom decide whose portfolio the user is trying to look at
- $querystring .= "&udom=$udom" if $udom;
- $querystring .= "&uname=$uname" if $uname;
-
- my $output .= <<'END_MYBLOCK';
- function openWindow(url, wdwName, w, h, toolbar,scrollbar) {
- var options = "width=" + w + ",height=" + h + ",";
- options += "resizable=yes,scrollbars="+scrollbar+",status=no,";
- options += "menubar=no,toolbar="+toolbar+",location=no,directories=no";
- var newWin = window.open(url, wdwName, options);
- newWin.focus();
- }
+# check for active blocking
+ my ($startblock,$endblock,$triggerblock) =
+ &blockcheck(\%setters,$activity,$uname,$udom,$url,$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') {
+ $querystring .= "&udom=$udom" if $udom;
+ $querystring .= "&uname=$uname" if $uname;
+ } 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);
+ $output = Apache::lonhtmlcommon::scripttag($output);
- my $popupUrl = "/adm/blockingstatus/$querystring";
- my $text = mt('Communication Blocked');
-
- $output .= <<"END_BLOCK";
+ my $popupUrl = "/adm/blockingstatus/$querystring";
+ my $text = &mt('Communication Blocked');
+ if ($activity eq 'docs') {
+ $text = &mt('Content Access Blocked');
+ } elsif ($activity eq 'printout') {
+ $text = &mt('Printing Blocked');
+ }
+ $output .= <<"END_BLOCK";
@@ -4125,19 +4968,19 @@ END_MYBLOCK
END_BLOCK
- return ($blocked, $output);
+ return ($blocked, $output);
}
###############################################
sub check_ip_acc {
- my ($acc)=@_;
+ my ($acc,$clientip)=@_;
&Apache::lonxml::debug("acc is $acc");
if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) {
return 1;
}
my $allowed=0;
- my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'};
+ my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip;
my $name;
foreach my $pattern (split(',',$acc)) {
@@ -4233,24 +5076,29 @@ sub get_domainconf {
if (keys(%{$domconfig{'login'}})) {
foreach my $key (keys(%{$domconfig{'login'}})) {
if (ref($domconfig{'login'}{$key}) eq 'HASH') {
- if ($key eq 'loginvia') {
- if (ref($domconfig{'login'}{'loginvia'}) eq 'HASH') {
- my @ids = &Apache::lonnet::current_machine_ids();
- foreach my $hostname (@ids) {
- if (ref($domconfig{'login'}{'loginvia'}{$hostname}) eq 'HASH') {
- 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'};
+ 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'};
+ }
}
- if ($domconfig{'login'}{'loginvia'}{$hostname}{'exempt'}) {
- $designhash{$udom.'.login.loginvia_exempt_'.$hostname} = $domconfig{'login'}{'loginvia'}{$hostname}{'exempt'};
+ } 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'};
+ }
}
}
}
@@ -4328,7 +5176,7 @@ sub get_legacy_domconf {
close($fh);
}
}
- if (-e '/home/httpd/html/adm/lonDomLogos/'.$udom.'.gif') {
+ if (-e $Apache::lonnet::perlvar{'lonDocRoot'}.'/adm/lonDomLogos/'.$udom.'.gif') {
$legacyhash{$udom.'.login.domlogo'} = "/adm/lonDomLogos/$udom.gif";
}
return %legacyhash;
@@ -4386,7 +5234,10 @@ sub designparm {
return $env{'environment.color.'.$which};
}
$domain=&determinedomain($domain);
- my %domdesign = &get_domainconf($domain);
+ my %domdesign;
+ unless ($domain eq 'public') {
+ %domdesign = &get_domainconf($domain);
+ }
my $output;
if ($domdesign{$domain.'.'.$which} ne '') {
$output = $domdesign{$domain.'.'.$which};
@@ -4411,27 +5262,39 @@ sub designparm {
=item * &authorspace()
-Inputs: ./.
+Inputs: $url (usually will be undef).
-Returns: Path to the Construction Space of the current user's
- accessed author space
- The author space will be that of the current user
- when accessing the own author space
- and that of the co-author/assistent co-author
- when accessing the co-author's/assistent co-author's
- space
+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 = '';
- if ($env{'request.role'} =~ /^ca|^aa/) {
- (undef,$caname) =
+ my $cadom = '';
+ if ($env{'request.role'} =~ /^(?:ca|aa)/) {
+ ($cadom,$caname) =
($env{'request.role'}=~/($match_domain)\/($match_username)$/);
- } else {
+ } elsif ($env{'request.role'} =~ m{^au\./($match_domain)/}) {
$caname = $env{'user.name'};
+ $cadom = $env{'user.domain'};
}
- return '/priv/'.$caname.'/';
+ if (($caname ne '') && ($cadom ne '')) {
+ return "/priv/$cadom/$caname/";
+ }
+ return;
}
##############################################
@@ -4449,7 +5312,7 @@ Returns: HTML div with $content
sub head_subbox {
my ($content)=@_;
my $output =
- ''
+ '
'
.$content
.'
'
}
@@ -4459,20 +5322,29 @@ sub head_subbox {
=item * &CSTR_pageheader()
-Inputs: ./.
+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 Construction Space pages
+ To be included on Authoring Space pages
=cut
sub CSTR_pageheader {
- # this is for resources; directories have customtitle, and crumbs
- # and select recent are created in lonpubdir.pm
- my ($uname,$thisdisfn)=
- ($env{'request.filename'} =~ m|^/home/([^/]+)/public_html/(.*)|);
- my $formaction='/priv/'.$uname.'/'.$thisdisfn;
- $formaction=~s/\/+/\//g;
+ 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 = '';
@@ -4486,10 +5358,10 @@ sub CSTR_pageheader {
my $output =
'
'
.&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it?
- .'
'.&mt('Construction Space:').' '
+ .'
'.&mt('Authoring Space:').' '
.'
';
+ }
$endbodytag=&Apache::lontexconvert::jsMath_process()."\n".$endbodytag;
if ( exists( $env{'internal.head.redirect'} ) ) {
if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) {
@@ -4801,7 +5689,7 @@ sub standard_css {
my $mono = 'monospace';
my $data_table_head = $sidebg;
my $data_table_light = '#FAFAFA';
- my $data_table_dark = '#F0F0F0';
+ my $data_table_dark = '#E0E0E0';
my $data_table_darker = '#CCCCCC';
my $data_table_highlight = '#FFFF00';
my $mail_new = '#FFBB77';
@@ -4841,7 +5729,6 @@ body {
a:focus,
a:focus img {
color: red;
- background: yellow;
}
form, .inline {
@@ -4856,6 +5743,14 @@ form, .inline {
vertical-align:middle;
}
+.LC_floatleft {
+ float: left;
+}
+
+.LC_floatright {
+ float: right;
+}
+
.LC_400Box {
width:400px;
}
@@ -4888,12 +5783,18 @@ form, .inline {
text-decoration:none;
}
+.LC_setting {
+ text-decoration:underline;
+}
+
.LC_error {
color: red;
- font-size: larger;
}
-.LC_warning,
+.LC_warning {
+ color: darkorange;
+}
+
.LC_diff_removed {
color: red;
}
@@ -4932,35 +5833,36 @@ div.LC_confirm_box .LC_success img {
}
.LC_discussion {
- background: $tabbg;
+ background: $data_table_dark;
border: 1px solid black;
margin: 2px;
}
-.LC_disc_action_links_bar {
- background: $tabbg;
- border: none;
- margin: 4px;
-}
-
.LC_disc_action_left {
+ background: $sidebg;
text-align: left;
+ padding: 4px;
+ margin: 2px;
}
.LC_disc_action_right {
+ background: $sidebg;
text-align: right;
+ padding: 4px;
+ margin: 2px;
}
.LC_disc_new_item {
background: white;
border: 2px solid red;
- margin: 2px;
+ margin: 4px;
+ padding: 4px;
}
.LC_disc_old_item {
background: white;
- border: 1px solid black;
- margin: 2px;
+ margin: 4px;
+ padding: 4px;
}
table.LC_pastsubmission {
@@ -5052,14 +5954,16 @@ td.LC_table_cell_checkbox {
overflow: hidden;
margin: 0;
padding: 0;
+ text-align: left;
}
-#LC_head_subbox {
+.LC_head_subbox, .LC_actionbox {
clear:both;
background: #F8F8F8; /* $sidebg; */
border: 1px solid $sidebg;
- margin: 0 0 10px 0;
+ margin: 0 0 10px 0;
padding: 3px;
+ text-align: left;
}
.LC_fontsize_medium {
@@ -5080,8 +5984,9 @@ td.LC_table_cell_checkbox {
vertical-align: middle;
}
-li.LC_menubuttons_inline_text img,a {
+li.LC_menubuttons_inline_text img {
cursor:pointer;
+ text-decoration: none;
}
.LC_menubuttons_link {
@@ -5129,14 +6034,6 @@ table.LC_nested {
width: 100%;
}
-.ui-accordion,
-.ui-accordion table.LC_data_table,
-.ui-accordion table.LC_nested_outer{
- border: 0px;
- border-spacing: 0px;
- margin: 3px;
-}
-
table.LC_data_table tr th,
table.LC_calendar tr th,
table.LC_prior_tries tr th,
@@ -5197,7 +6094,8 @@ table.LC_nested tr.LC_empty_row td {
padding: 8px;
}
-table.LC_data_table tr.LC_empty_row td {
+table.LC_data_table tr.LC_empty_row td,
+table.LC_data_table tr.LC_footer_row td {
background-color: $sidebg;
}
@@ -5252,22 +6150,6 @@ table.LC_nested tr td.LC_right_item {
text-align: right;
}
-.ui-accordion table.LC_nested tr.LC_odd_row td.LC_left_item,
-.ui-accordion table.LC_nested tr.LC_even_row td.LC_left_item {
- text-align: right;
- width: 40%;
- padding-right:10px;
- vertical-align: top;
- padding: 5px;
-}
-
-.ui-accordion table.LC_nested tr.LC_odd_row td.LC_right_item,
-.ui-accordion table.LC_nested tr.LC_even_row td.LC_right_item {
- text-align: left;
- width: 60%;
- padding: 2px 4px;
-}
-
table.LC_nested tr.LC_odd_row td {
background-color: #EEEEEE;
}
@@ -5399,6 +6281,11 @@ span.LC_current_location {
background: $pgbg;
}
+span.LC_current_nav_location {
+ font-weight:bold;
+ background: $sidebg;
+}
+
span.LC_parm_menu_item {
font-size: larger;
}
@@ -5426,6 +6313,14 @@ span.LC_parm_symb {
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,
@@ -5762,7 +6657,6 @@ div.LC_docs_entry_move {
table.LC_data_table tr > td.LC_docs_entry_commands,
table.LC_data_table tr > td.LC_docs_entry_parameter {
- background: #DDDDDD;
font-size: x-small;
}
@@ -5892,6 +6786,7 @@ div.LC_edit_problem_footer {
font-weight: normal;
font-size: medium;
margin: 2px;
+ background-color: $sidebg;
}
div.LC_edit_problem_header,
@@ -5901,6 +6796,7 @@ div.LC_edit_problem_footer div,
div.LC_edit_problem_editxml_header,
div.LC_edit_problem_editxml_header div {
margin-top: 5px;
+ z-index: 100;
}
div.LC_edit_problem_header_title {
@@ -5908,6 +6804,7 @@ div.LC_edit_problem_header_title {
font-size: larger;
background: $tabbg;
padding: 3px;
+ margin: 0 0 5px 0;
}
table.LC_edit_problem_header_title {
@@ -5917,7 +6814,12 @@ table.LC_edit_problem_header_title {
div.LC_edit_problem_discards {
float: left;
- padding-bottom: 5px;
+}
+
+div.LC_edit_actionbar {
+ margin: -5px 0px 0px 0px !important;
+ background-color: $sidebg;
+ height: 31px;
}
div.LC_edit_problem_saves {
@@ -5925,6 +6827,23 @@ div.LC_edit_problem_saves {
padding-bottom: 5px;
}
+.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;
@@ -5939,13 +6858,13 @@ div.LC_createcourse {
}
.LC_dccid {
+ float: right;
margin: 0.2em 0 0 0;
padding: 0;
font-size: 90%;
display:none;
}
-a:hover,
ol.LC_primary_menu a:hover,
ol#LC_MenuBreadcrumbs a:hover,
ol#LC_PathBreadcrumbs a:hover,
@@ -6019,6 +6938,7 @@ fieldset > legend {
#LC_nav_bar {
float: left;
+ background-color: $pgbg_or_bgcolor;
margin: 0 0 2px 0;
}
@@ -6027,6 +6947,7 @@ fieldset > legend {
padding: 0;
font-weight: bold;
text-align: center;
+ background-color: $pgbg_or_bgcolor;
}
#LC_nav_bar em {
@@ -6034,14 +6955,9 @@ fieldset > legend {
font-style: normal;
}
-/* Preliminary fix to hide nav_bar inside bookmarks window */
-#LC_bookmarks #LC_nav_bar {
- display:none;
-}
-
ol.LC_primary_menu {
- float: right;
margin: 0;
+ padding: 0;
}
ol#LC_PathBreadcrumbs {
@@ -6049,14 +6965,91 @@ ol#LC_PathBreadcrumbs {
}
ol.LC_primary_menu li {
- display: inline;
- padding: 5px 5px 0 10px;
+ 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 {
@@ -6069,23 +7062,85 @@ ol.LC_primary_menu a.LC_new_message {
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: both;
+ 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;
- display: inline;
- vertical-align: middle;
+ 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 {
@@ -6093,7 +7148,7 @@ ul.LC_TabContent {
background: $sidebg;
border-bottom: solid 1px $lg_border_color;
list-style:none;
- margin: 0 -10px;
+ margin: -1px -10px 0 -10px;
padding: 0;
}
@@ -6116,7 +7171,7 @@ ul.LC_TabContent li {
padding: 0 16px 0 10px;
background-color:$tabbg;
border-bottom:solid 1px $lg_border_color;
- border-right: solid 1px $font;
+ border-left: solid 1px $font;
}
ul.LC_TabContent .right {
@@ -6156,6 +7211,12 @@ ul.LC_TabContent li.active a {
background:#FFFFFF;
outline: none;
}
+
+ul.LC_TabContent li.goback {
+ float: left;
+ border-left: none;
+}
+
#maincoursedoc {
clear:both;
}
@@ -6210,17 +7271,15 @@ ul.LC_TabContentBigger li.active b {
background:url('/adm/lonIcons/tabbgright.gif') right top no-repeat;
color:$font;
border: 0;
- cursor:default;
}
ul.LC_CourseBreadcrumbs {
background: $sidebg;
- line-height: 32px;
+ height: 2em;
padding-left: 10px;
- margin: 0 0 10px 0;
+ margin: 0;
list-style-position: inside;
-
}
ol#LC_MenuBreadcrumbs,
@@ -6262,6 +7321,11 @@ ol#LC_PathBreadcrumbs li a {
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;
@@ -6402,6 +7466,10 @@ a#LC_content_toolbar_changefolder_toggle
background-image:url(/res/adm/pages/open-all-folders.gif);
}
+a#LC_content_toolbar_edittoplevel {
+ background-image:url(/res/adm/pages/edittoplevel.gif);
+}
+
ul#LC_toolbar li a:hover {
background-position: bottom center;
}
@@ -6412,6 +7480,7 @@ ul#LC_toolbar {
list-style:none;
position:relative;
background-color:white;
+ overflow: auto;
}
ul#LC_toolbar li {
@@ -6421,6 +7490,7 @@ ul#LC_toolbar li {
float: left;
display:inline;
vertical-align:middle;
+ white-space: nowrap;
}
@@ -6466,12 +7536,78 @@ ul.LC_funclist li {
line-height: 150%;
}
-.ui-accordion .LC_advanced_toggle {
- float: right;
- font-size: 90%;
- padding: 0px 4px
+.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 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;}
+
END
}
@@ -6508,6 +7644,7 @@ sub headtag {
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(),
@@ -6518,7 +7655,12 @@ sub headtag {
my $result =
'
'.
- &font_settings();
+ &font_settings($args);
+
+ my $inhibitprint;
+ if ($args->{'print_suppress'}) {
+ $inhibitprint = &print_suppression();
+ }
if (!$args->{'frameset'}) {
$result .= &Apache::lonhtmlcommon::htmlareaheaders();
@@ -6529,9 +7671,25 @@ sub headtag {
if (!$args->{'no_nav_bar'}
&& !$args->{'only_body'}
&& !$args->{'frameset'}) {
- $result .= &help_menu_js();
+ $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);
@@ -6548,8 +7706,18 @@ ADDMETA
}
if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); }
$result .= '
LON-CAPA '.$title.' '
- .' '
+ .' {'frameset'}) {
+ $result .= ' /';
+ }
+ $result .= '>'
+ .$inhibitprint
.$head_extra;
+ if ($env{'browser.mobile'}) {
+ $result .= '
+
+ ';
+ }
return $result.'';
}
@@ -6559,21 +7727,103 @@ ADDMETA
Returns neccessary to set the proper encoding
-Inputs: none
+Inputs: optional reference to HASH -- $args passed to &headtag()
=cut
sub font_settings {
+ my ($args) = @_;
my $headerstring='';
- if (!$env{'browser.mathml'} && $env{'browser.unicode'}) {
- $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
@@ -6583,6 +7833,7 @@ Inputs: none
=cut
sub xml_begin {
+ my ($is_frameset) = @_;
my $output='';
if ($env{'browser.mathml'}) {
@@ -6594,9 +7845,12 @@ sub xml_begin {
.''
.'';
+ } elsif ($is_frameset) {
+ $output=''."\n".
+ ''."\n";
} else {
- $output=''
- .'';
+ $output=''."\n".
+ ''."\n";
}
return $output;
}
@@ -6645,7 +7899,9 @@ $args - additional optional args support
should it have jsmath forced on by the
current page
bread_crumbs -> Array containing breadcrumbs
- bread_crumbs_components -> if exists show it as headline else show only the breadcrumbs
+ bread_crumbs_component -> if exists show it as headline else show only the breadcrumbs
+ group -> includes the current group, if page is for a
+ specific group
=back
@@ -6656,32 +7912,12 @@ $args - additional optional args support
sub start_page {
my ($title,$head_extra,$args) = @_;
#&Apache::lonnet::logthis("start_page ".join(':',caller(0)));
-#SD
-#I don't see why we copy certain elements of %$args to %head_args
-#head args is passed to headtag() and this routine only reads those
-#keys that are needed. There doesn't happen any writes or any processing
-#of other keys.
-#proposal: just pass $args to headtag instead of \%head_args and delete
-#marked lines
-#<- MARK
- my %head_args;
- foreach my $arg ('redirect','force_register','domain','function',
- 'bgcolor','frameset','no_nav_bar','only_body',
- 'no_auto_mt_title') {
- if (defined($args->{$arg})) {
- $head_args{$arg} = $args->{$arg};
- }
- }
-#MARK ->
$env{'internal.start_page'}++;
- my $result;
+ my ($result,@advtools);
if (! exists($args->{'skip_phases'}{'head'}) ) {
- $result .=
- &xml_begin() . &headtag($title,$head_extra,\%head_args);
-#replace prev line by
-# &xml_begin() . &headtag($title, $head_extra, $args);
+ $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args);
}
if (! exists($args->{'skip_phases'}{'body'}) ) {
@@ -6695,7 +7931,8 @@ sub start_page {
$args->{'function'}, $args->{'add_entries'},
$args->{'only_body'}, $args->{'domain'},
$args->{'force_register'}, $args->{'no_nav_bar'},
- $args->{'bgcolor'}, $args);
+ $args->{'bgcolor'}, $args,
+ \@advtools);
}
}
@@ -6724,6 +7961,10 @@ sub start_page {
&Apache::lonhtmlcommon::add_breadcrumb($crumb);
}
}
+ # if @advtools array contains items add then to the breadcrumbs
+ if (@advtools > 0) {
+ &Apache::lonmenu::advtools_crumbs(@advtools);
+ }
#if bread_crumbs_component exists show it as headline else show only the breadcrumbs
if(exists($args->{'bread_crumbs_component'})){
@@ -6747,13 +7988,14 @@ sub end_page {
}
$result .= &Apache::lonxml::xmlend($target,$parser);
}
-
if ($args->{'frameset'}) {
$result .= '';
} else {
$result .= &endbodytag($args);
}
- $result .= "\n";
+ unless ($args->{'notbody'}) {
+ $result .= "\n";
+ }
if ($args->{'js_ready'}) {
$result = &js_ready($result);
@@ -6766,6 +8008,292 @@ sub end_page {
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) = @_;
@@ -6773,6 +8301,7 @@ sub html_encode {
return $result;
}
+
sub js_ready {
my ($result) = @_;
@@ -6809,11 +8338,128 @@ sub validate_page {
}
}
+
+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) = @_;
+ 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).
- &mt($msg).
+ ''.$msg.'
'.
&Apache::loncommon::end_page();
if (ref($r)) {
$r->print($page);
@@ -6836,10 +8482,14 @@ sub simple_error_page {
}
sub start_data_table {
- my ($add_class) = @_;
+ 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";
+ return ''."\n";
}
sub end_data_table {
@@ -6848,18 +8498,20 @@ sub simple_error_page {
}
sub start_data_table_row {
- my ($add_class) = @_;
+ 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 '');
- return ''."\n";;
+ $id = (' id="'.$id.'"') unless ($id eq '');
+ return ' '."\n";
}
sub continue_data_table_row {
- my ($add_class) = @_;
+ 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 '');;
- return ' '."\n";;
+ $css_class = (join(' ',$css_class,$add_class)) unless ($add_class eq '');
+ $id = (' id="'.$id.'"') unless ($id eq '');
+ return ' '."\n";
}
sub end_data_table_row {
@@ -6964,7 +8616,7 @@ sub get_users_function {
$function='admin';
}
if (($env{'request.role'}=~/^(au|ca|aa)/) ||
- ($ENV{'REQUEST_URI'}=~/^(\/priv|\~)/)) {
+ ($ENV{'REQUEST_URI'}=~ m{/^(/priv)})) {
$function='author';
}
return $function;
@@ -7023,7 +8675,7 @@ role status: active, previous or future.
sub check_user_status {
my ($udom,$uname,$cdom,$crs,$role,$sec) = @_;
my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname);
- my @uroles = keys %userinfo;
+ my @uroles = keys(%userinfo);
my $srchstr;
my $active_chk = 'none';
my $now = time;
@@ -7100,7 +8752,19 @@ sub get_sections {
my %sectioncount;
my $now = time;
- if (!defined($possible_roles) || (grep(/^st$/,@$possible_roles))) {
+ 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();
@@ -7127,6 +8791,9 @@ sub get_sections {
}
}
}
+ 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; }
@@ -7274,7 +8941,7 @@ sub get_course_users {
active => 'Active',
future => 'Future',
);
- my %nothide;
+ my (%nothide,@possdoms);
if ($hidepriv) {
my %coursehash=&Apache::lonnet::coursedescription($cdom.'_'.$cnum);
foreach my $user (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) {
@@ -7284,6 +8951,10 @@ sub get_course_users {
$nothide{$user} = 1;
}
}
+ my @possdoms = ($cdom);
+ if ($coursehash{'checkforpriv'}) {
+ push(@possdoms,split(/,/,$coursehash{'checkforpriv'}));
+ }
}
foreach my $person (sort(keys(%coursepersonnel))) {
my $match = 0;
@@ -7319,7 +8990,7 @@ sub get_course_users {
}
if ($uname ne '' && $udom ne '') {
if ($hidepriv) {
- if ((&Apache::lonnet::privileged($uname,$udom)) &&
+ if ((&Apache::lonnet::privileged($uname,$udom,\@possdoms)) &&
(!$nothide{$uname.':'.$udom})) {
next;
}
@@ -7407,14 +9078,19 @@ sub get_user_info {
=item * &get_user_quota()
-Retrieves quota assigned for storage of portfolio files for a user
+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 or community, if quota name is
+ course
Returns:
-1. Disk quota (in Mb) assigned to student.
+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).
@@ -7425,7 +9101,7 @@ Returns:
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 instituional status(es) in the domain.
+defined for the user's institutional status(es) in the domain.
=cut
@@ -7433,7 +9109,7 @@ defined for the user's instituional stat
sub get_user_quota {
- my ($uname,$udom) = @_;
+ my ($uname,$udom,$quotaname,$crstype) = @_;
my ($quota,$quotatype,$settingstatus,$defquota);
if (!defined($udom)) {
$udom = $env{'user.domain'};
@@ -7448,27 +9124,58 @@ sub get_user_quota {
$defquota = 0;
} else {
my $inststatus;
- if ($udom eq $env{'user.domain'} && $uname eq $env{'user.name'}) {
- $quota = $env{'environment.portfolioquota'};
- $inststatus = $env{'environment.inststatus'};
- } else {
- my %userenv =
- &Apache::lonnet::get('environment',['portfolioquota',
- 'inststatus'],$udom,$uname);
- my ($tmp) = keys(%userenv);
- if ($tmp !~ /^(con_lost|error|no_such_host)/i) {
- $quota = $userenv{'portfolioquota'};
- $inststatus = $userenv{'inststatus'};
- } else {
- undef(%userenv);
- }
- }
- ($defquota,$settingstatus) = &default_quota($udom,$inststatus);
- if ($quota eq '') {
- $quota = $defquota;
- $quotatype = 'default';
+ 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 {
- $quotatype = 'custom';
+ 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')) {
+ $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) {
@@ -7488,54 +9195,60 @@ Retrieves default quota assigned for sto
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.
+ 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.
+
+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).
+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.
-=back
-
=cut
###############################################
sub default_quota {
- my ($udom,$inststatus) = @_;
+ 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'}{'defaultquota'}) eq 'HASH') {
- if ($quotahash{'quotas'}{'defaultquota'}{$item} ne '') {
+ if (ref($quotahash{'quotas'}{$key}) eq 'HASH') {
+ if ($quotahash{'quotas'}{$key}{$item} ne '') {
if ($defquota eq '') {
- $defquota = $quotahash{'quotas'}{'defaultquota'}{$item};
+ $defquota = $quotahash{'quotas'}{$key}{$item};
$settingstatus = $item;
- } elsif ($quotahash{'quotas'}{'defaultquota'}{$item} > $defquota) {
- $defquota = $quotahash{'quotas'}{'defaultquota'}{$item};
+ } elsif ($quotahash{'quotas'}{$key}{$item} > $defquota) {
+ $defquota = $quotahash{'quotas'}{$key}{$item};
$settingstatus = $item;
}
}
- } else {
+ } elsif ($key eq 'defaultquota') {
if ($quotahash{'quotas'}{$item} ne '') {
if ($defquota eq '') {
$defquota = $quotahash{'quotas'}{$item};
@@ -7549,16 +9262,25 @@ sub default_quota {
}
}
if ($defquota eq '') {
- if (ref($quotahash{'quotas'}{'defaultquota'}) eq 'HASH') {
- $defquota = $quotahash{'quotas'}{'defaultquota'}{'default'};
- } else {
+ 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';
- $defquota = 20;
+ if ($quotaname eq 'author') {
+ $defquota = 500;
+ } else {
+ $defquota = 20;
+ }
}
if (wantarray) {
return ($defquota,$settingstatus);
@@ -7567,6 +9289,64 @@ sub default_quota {
}
}
+###############################################
+
+=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, community or textbook).
+
+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);
@@ -7605,7 +9385,7 @@ sub get_secgrprole_info {
}
sub user_picker {
- my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype) = @_;
+ my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context) = @_;
my $currdom = $dom;
my %curr_selected = (
srchin => 'dom',
@@ -7696,10 +9476,15 @@ sub user_picker {
$srchtypesel .= "\n \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 $env{'request.role.domain'}) {
+ 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 {
@@ -7738,7 +9523,7 @@ function setSearch(createnew,callingForm
}
}
for (var i=0; i 0) {
- $output = ' '.&mt("$text{$check} with the following format(s) may only be used for verified users at [_1]:",$domdesc).' ';
+ $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})) {
@@ -8000,7 +9788,14 @@ sub personal_data_fieldtitles {
sub sorted_inst_types {
my ($dom) = @_;
- my ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($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');
@@ -8073,7 +9868,8 @@ sub get_standard_codeitems {
=item * sorted_slots()
-Sorts an array of slot names in order of slot start time (earliest first).
+Sorts an array of slot names in order of an optional sort key,
+default sort is by slot start time (earliest first).
Inputs:
@@ -8083,15 +9879,16 @@ slotsarr - Reference to array of unsort
slots - Reference to hash of hash, where outer hash keys are slot names.
+sortkey - Name of key in inner hash to be sorted on (e.g., starttime).
+
=back
Returns:
=over 4
-sorted - An array of slot names sorted by the start time of the slot.
-
-=back
+sorted - An array of slot names sorted by a specified sort key
+ (default sort key is start time of the slot).
=back
@@ -8099,13 +9896,16 @@ sorted - An array of slot names sorted
sub sorted_slots {
- my ($slotsarr,$slots) = @_;
+ my ($slotsarr,$slots,$sortkey) = @_;
+ if ($sortkey eq '') {
+ $sortkey = 'starttime';
+ }
my @sorted;
if ((ref($slotsarr) eq 'ARRAY') && (ref($slots) eq 'HASH')) {
@sorted =
sort {
if (ref($slots->{$a}) && ref($slots->{$b})) {
- return $slots->{$a}{'starttime'} <=> $slots->{$b}{'starttime'}
+ return $slots->{$a}{$sortkey} <=> $slots->{$b}{$sortkey}
}
if (ref($slots->{$a})) { return -1;}
if (ref($slots->{$b})) { return 1;}
@@ -8115,9 +9915,136 @@ sub sorted_slots {
return @sorted;
}
+=pod
+
+=item * get_future_slots()
+
+Inputs:
+
+=over 4
+
+cnum - course number
+
+cdom - course domain
+
+now - current UNIX time
+
+symb - optional symb
+
+=back
+
+Returns:
+
+=over 4
+
+sorted_reservable - ref to array of student_schedulable slots currently
+ reservable, ordered by end date of reservation period.
+
+reservable_now - ref to hash of student_schedulable slots currently
+ reservable.
+
+ Keys in inner hash are:
+ (a) symb: either blank or symb to which slot use is restricted.
+ (b) endreserve: end date of reservation period.
+
+sorted_future - ref to array of student_schedulable slots reservable in
+ the future, ordered by start date of reservation period.
+
+future_reservable - ref to hash of student_schedulable slots reservable
+ in the future.
+
+ Keys in inner hash are:
+ (a) symb: either blank or symb to which slot use is restricted.
+ (b) startreserve: start date of reservation period.
+
+=back
+
+=cut
+
+sub get_future_slots {
+ my ($cnum,$cdom,$now,$symb) = @_;
+ my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future);
+ my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom);
+ foreach my $slot (keys(%slots)) {
+ next unless($slots{$slot}->{'type'} eq 'schedulable_student');
+ if ($symb) {
+ next if (($slots{$slot}->{'symb'} ne '') &&
+ ($slots{$slot}->{'symb'} ne $symb));
+ }
+ if (($slots{$slot}->{'starttime'} > $now) &&
+ ($slots{$slot}->{'endtime'} > $now)) {
+ if (($slots{$slot}->{'allowedsections'}) || ($slots{$slot}->{'allowedusers'})) {
+ my $userallowed = 0;
+ if ($slots{$slot}->{'allowedsections'}) {
+ my @allowed_sec = split(',',$slots{$slot}->{'allowedsections'});
+ if (!defined($env{'request.role.sec'})
+ && grep(/^No section assigned$/,@allowed_sec)) {
+ $userallowed=1;
+ } else {
+ if (grep(/^\Q$env{'request.role.sec'}\E$/,@allowed_sec)) {
+ $userallowed=1;
+ }
+ }
+ unless ($userallowed) {
+ if (defined($env{'request.course.groups'})) {
+ my @groups = split(/:/,$env{'request.course.groups'});
+ foreach my $group (@groups) {
+ if (grep(/^\Q$group\E$/,@allowed_sec)) {
+ $userallowed=1;
+ last;
+ }
+ }
+ }
+ }
+ }
+ if ($slots{$slot}->{'allowedusers'}) {
+ my @allowed_users = split(',',$slots{$slot}->{'allowedusers'});
+ my $user = $env{'user.name'}.':'.$env{'user.domain'};
+ if (grep(/^\Q$user\E$/,@allowed_users)) {
+ $userallowed = 1;
+ }
+ }
+ next unless($userallowed);
+ }
+ my $startreserve = $slots{$slot}->{'startreserve'};
+ my $endreserve = $slots{$slot}->{'endreserve'};
+ my $symb = $slots{$slot}->{'symb'};
+ if (($startreserve < $now) &&
+ (!$endreserve || $endreserve > $now)) {
+ my $lastres = $endreserve;
+ if (!$lastres) {
+ $lastres = $slots{$slot}->{'starttime'};
+ }
+ $reservable_now{$slot} = {
+ symb => $symb,
+ endreserve => $lastres
+ };
+ } elsif (($startreserve > $now) &&
+ (!$endreserve || $endreserve > $startreserve)) {
+ $future_reservable{$slot} = {
+ symb => $symb,
+ startreserve => $startreserve
+ };
+ }
+ }
+ }
+ my @unsorted_reservable = keys(%reservable_now);
+ if (@unsorted_reservable > 0) {
+ @sorted_reservable =
+ &sorted_slots(\@unsorted_reservable,\%reservable_now,'endreserve');
+ }
+ my @unsorted_future = keys(%future_reservable);
+ if (@unsorted_future > 0) {
+ @sorted_future =
+ &sorted_slots(\@unsorted_future,\%future_reservable,'startreserve');
+ }
+ return (\@sorted_reservable,\%reservable_now,\@sorted_future,\%future_reservable);
+}
=pod
+=back
+
=head1 HTTP Helpers
=over 4
@@ -8256,68 +10183,649 @@ sub get_env_multiple {
sub ask_for_embedded_content {
my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
- my $upload_output = '
- '."\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];
+ }
+}
- $upload_output.=''.&mt("Invalid characters").' ';
+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 {
- $upload_output .='
-
- ';
- my $attrib = join(':',@{$$allfiles{$embed_file}});
- $upload_output .=
- "\n\t\t".
- ' ';
- if (exists($$codebase{$embed_file})) {
- $upload_output .=
- "\n\t\t".
- ' ';
- }
- }
- $upload_output .= ''.&Apache::loncommon::end_data_table_row();
- $num++;
- }
- $upload_output .= &Apache::loncommon::end_data_table().'
-
-
- '.&mt('(only files for which a location has been provided will be uploaded)').'
- ';
- return $upload_output;
+ 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) = @_;
- my $output;
+ $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'};
-
- $env{'form.embedded_orig_'.$i} =
- &unescape($env{'form.embedded_orig_'.$i});
+ 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} };
-
- $path = $env{'form.currentpath'}.$path;
$fname = &Apache::lonnet::clean_filename($fname);
# See if there is anything left
next if ($fname eq '');
@@ -8329,12 +10837,12 @@ sub upload_embedded {
if ($group ne '') {
$port_path = "groups/$group/$port_path";
}
- ($state,$msg) = &check_for_upload($path,$fname,$group,'embedded_item_'.$i,
+ ($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'
- || $state eq 'file_exists' ) {
+ || $state eq 'file_locked') {
$output .= $msg;
next;
}
@@ -8348,31 +10856,59 @@ sub upload_embedded {
# Check if extension is valid
if (($fname =~ /\.(\w+)$/) &&
(&Apache::loncommon::fileembstyle($1) eq 'hdn')) {
- $output .= &mt('Invalid file extension ([_1]) - reserved for LONCAPA use - rename the file with a different extension and re-upload. ',$1);
+ $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);
+ $output .= &mt('Unrecognized file extension ([_1]) - rename the file with a proper extension and re-upload.',$1).' ';
next;
} elsif ($fname=~/\.(\d+)\.(\w+)$/) {
- $output .= &mt('File name not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2);
+ $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=
- &Apache::lonnet::userfileupload('embedded_item_'.$i,'',
- $dirpath.$path);
+ 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].'
+ .&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].'
,$result,$orig_uploaded_filename,$env{'form.embedded_orig_'.$i})
- .' ';
- next;
+ .' ';
+ next;
} else {
- $output .= ''.&mt('Uploaded [_1]',''.
- $path.$fname.' ').'
';
+ $output .= &mt('Uploaded [_1]',''.
+ $path.$fname.' ').' ';
+ if ($context eq 'syllabus') {
+ &Apache::lonnet::make_public_indefinitely($result);
+ }
}
} else {
# Save the file
@@ -8380,12 +10916,12 @@ sub upload_embedded {
my $fullpath = $dir_root.$dirpath.'/'.$path;
my $dest = $fullpath.$fname;
my $url = $url_root.$dirpath.'/'.$path.$fname;
- my @parts=split(/\//,$fullpath);
+ my @parts=split(/\//,"$dirpath/$path");
my $count;
my $filepath = $dir_root;
- for ($count=4;$count<=$#parts;$count++) {
- $filepath .= "/$parts[$count]";
- if ((-e $filepath)!=1) {
+ foreach my $subdir (@parts) {
+ $filepath .= "/$subdir";
+ if (!-e $filepath) {
mkdir($filepath,0770);
}
}
@@ -8393,30 +10929,295 @@ sub upload_embedded {
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}).
+ &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}).
+ &mt('An error occurred while writing the file [_1] for embedded element [_2].',
+ $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
' ';
} else {
- if ($context eq 'testbank') {
- $output .= &mt('Embedded file uploaded successfully:').
- ' '.
- $orig_uploaded_filename.' ';
- } else {
- $output .= ''.
- &mt('View embedded file: [_1]',''.
- $orig_uploaded_filename.' ').' ';
+ $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".
+ ''.&mt('For consistency between the reference(s) and the location of the corresponding stored file within LON-CAPA.').' '."\n".
+ ''.&mt('To change absolute paths to relative paths, or replace directory traversal via "../" within the original reference.').' '."\n".
+ ' '."\n".''.
+ &mt('LON-CAPA can make the required changes to your HTML file.').'
'."\n".
+ ''."\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;
}
- return $output;
}
sub check_for_existing {
@@ -8438,48 +11239,1474 @@ sub check_for_existing {
sub check_for_upload {
my ($path,$fname,$group,$element,$portfolio_root,$port_path,
$disk_quota,$current_disk_usage,$uname,$udom) = @_;
- my $filesize = (length($env{'form.'.$element})) / 1000; #express in k (1024?)
+ my $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 @dir_list = &Apache::lonnet::dirlist($portfolio_root.$path,$udom,$uname,
- $getpropath);
+ my ($dirlistref,$listerror) =
+ &Apache::lonnet::dirlist($portfolio_root.$path,$udom,$uname,$getpropath);
my $found_file = 0;
my $locked_file = 0;
- foreach my $line (@dir_list) {
- my ($file_name)=split(/\&/,$line,2);
- if ($file_name eq $fname){
- $file_name = $path.$file_name;
- if ($group ne '') {
- $file_name = $group.$file_name;
- }
- $found_file = 1;
- if (&Apache::lonnet::is_locked($file_name,$udom,$uname) eq 'true') {
- $locked_file = 1;
+ my @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);
+ 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 = '';
+ 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 .= '';
$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('Unable to upload [_1]. A file by that name was found in [_2].',''.$fname.' ',$port_path.$env{'form.currentpath'});
- $msg .= ' ';
- $msg .= ' ';
- $msg .= &mt('To upload, rename or delete existing [_1] in [_2].',''.$fname.' ', $port_path.$env{'form.currentpath'});
- return ('file_exists',$msg);
+ 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".
+ '';
+ }
+ }
+ $output .= '
+$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 .= ''."\n".
+ &mt('Used by:').' '."\n".
+ ' '."\n".
+ ' '."\n".
+ '
';
+ } elsif ($action eq 'display') {
+ $output .= ''."\n".
+ &mt('Title:').' '."\n".
+ '
';
+ }
+ $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";
+';
+}
+
+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 .= < 0) {
+ dependencyCheck(form,count,offset);
+ var item = (offset+$startcount)+7*(count-1);
+ form.elements[item].checked = true;
+ if(Object.prototype.toString.call(parents[count]) === '[object Array]') {
+ if (parents[count].length > 0) {
+ for (var j=0; j 0) {
+ var chosen = (offset+$startcount)+7*(count-1);
+ var depitem = $startcount + ((count-1) * 7) + 4;
+ var currtype = form.elements[depitem].type;
+ if (form.elements[chosen].value == 'dependency') {
+ document.getElementById('arc_depon_'+count).style.display='block';
+ form.elements[depitem].options.length = 0;
+ form.elements[depitem].options[0] = new Option('Select','',true,true);
+ for (var i=1; i<=numitems; i++) {
+ if (i == count) {
+ continue;
+ }
+ var startelement = $startcount + (i-1) * 7;
+ for (var j=1; j<6; j++) {
+ if ((j != 2) && (j!= 4)) {
+ var item = startelement + j;
+ if (form.elements[item].type == 'radio') {
+ if (form.elements[item].checked) {
+ if (form.elements[item].value == 'display') {
+ var n = form.elements[depitem].options.length;
+ form.elements[depitem].options[n] = new Option(titles[i],i,false,false);
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ document.getElementById('arc_depon_'+count).style.display='none';
+ form.elements[depitem].options.length = 0;
+ form.elements[depitem].options[0] = new Option('Select','',true,true);
+ }
+ titleCheck(form,count,offset);
+ }
+}
+
+function propagateSelect(form,count,offset) {
+ if (count > 0) {
+ var item = (1+offset+$startcount)+7*(count-1);
+ var picked = form.elements[item].options[form.elements[item].selectedIndex].value;
+ if (Object.prototype.toString.call(parents[count]) === '[object Array]') {
+ if (parents[count].length > 0) {
+ for (var j=0; j 0) {
+ var item = (offset+$startcount)+7*(count-1);
+ if (form.elements[item].type == 'radio') {
+ if (form.elements[item].value == 'dependency') {
+ if (form.elements[item+1].type == 'select-one') {
+ for (var i=0; i 0) {
+ for (var j=0; j 0) {
+ var chosen = (offset+$startcount)+7*(count-1);
+ var depitem = $startcount + ((count-1) * 7) + 2;
+ var currtype = form.elements[depitem].type;
+ if (form.elements[chosen].value == 'display') {
+ document.getElementById('arc_title_'+count).style.display='block';
+ if ((count==1) && ((parents[count].length > 0) || (numitems == 1))) {
+ document.getElementById('archive_title_'+count).value=maintitle;
+ }
+ } else {
+ document.getElementById('arc_title_'+count).style.display='none';
+ if (currtype == 'text') {
+ document.getElementById('archive_title_'+count).value='';
+ }
+ }
+ }
+ return;
+}
+
+// ]]>
+
+END
+ return $scripttag;
+}
+
+sub process_extracted_files {
+ my ($context,$docudom,$docuname,$destination,$dir_root,$hiddenelem) = @_;
+ my $numitems = $env{'form.archive_count'};
+ return unless ($numitems);
+ my @ids=&Apache::lonnet::current_machine_ids();
+ my ($prefix,$pathtocheck,$dir,$ishome,$error,$warning,%toplevelitems,%is_dir,
+ %folders,%containers,%mapinner,%prompttofetch);
+ my $docuhome = &Apache::lonnet::homeserver($docuname,$docudom);
+ if (grep(/^\Q$docuhome\E$/,@ids)) {
+ $prefix = &LONCAPA::propath($docudom,$docuname);
+ $pathtocheck = "$dir_root/$destination";
+ $dir = $dir_root;
+ $ishome = 1;
+ } else {
+ $prefix = $Apache::lonnet::perlvar{'lonDocRoot'};
+ $pathtocheck = "$dir_root/$docudom/$docuname/$destination";
+ $dir = "$dir_root/$docudom/$docuname";
+ }
+ my $currdir = "$dir_root/$destination";
+ (my $docstype,$mapinner{'0'}) = ($destination =~ m{^(docs|supplemental)/(\w+)/});
+ if ($env{'form.folderpath'}) {
+ my @items = split('&',$env{'form.folderpath'});
+ $folders{'0'} = $items[-2];
+ if ($env{'form.folderpath'} =~ /\:1$/) {
+ $containers{'0'}='page';
+ } else {
+ $containers{'0'}='sequence';
+ }
+ }
+ my @archdirs = &get_env_multiple('form.archive_directory');
+ if ($numitems) {
+ for (my $i=1; $i<=$numitems; $i++) {
+ my $path = $env{'form.archive_content_'.$i};
+ if ($path =~ m{^\Q$pathtocheck\E/([^/]+)$}) {
+ my $item = $1;
+ $toplevelitems{$item} = $i;
+ if (grep(/^\Q$i\E$/,@archdirs)) {
+ $is_dir{$item} = 1;
+ }
+ }
+ }
+ }
+ my ($output,%children,%parent,%titles,%dirorder,$result);
+ if (keys(%toplevelitems) > 0) {
+ my @contents = sort(keys(%toplevelitems));
+ (my $count,undef) = &get_extracted($docudom,$docuname,$currdir,\%is_dir,\%children,
+ \%parent,\@contents,\%dirorder,\%titles);
+ }
+ my (%referrer,%orphaned,%todelete,%todeletedir,%newdest,%newseqid);
+ if ($numitems) {
+ for (my $i=1; $i<=$numitems; $i++) {
+ next if ($env{'form.archive_'.$i} eq 'dependency');
+ my $path = $env{'form.archive_content_'.$i};
+ if ($path =~ /^\Q$pathtocheck\E/) {
+ if ($env{'form.archive_'.$i} eq 'discard') {
+ if ($prefix ne '' && $path ne '') {
+ if (-e $prefix.$path) {
+ if ((@archdirs > 0) &&
+ (grep(/^\Q$i\E$/,@archdirs))) {
+ $todeletedir{$prefix.$path} = 1;
+ } else {
+ $todelete{$prefix.$path} = 1;
+ }
+ }
+ }
+ } elsif ($env{'form.archive_'.$i} eq 'display') {
+ my ($docstitle,$title,$url,$outer);
+ ($title) = ($path =~ m{/([^/]+)$});
+ $docstitle = $env{'form.archive_title_'.$i};
+ if ($docstitle eq '') {
+ $docstitle = $title;
+ }
+ $outer = 0;
+ if (ref($dirorder{$i}) eq 'ARRAY') {
+ if (@{$dirorder{$i}} > 0) {
+ foreach my $item (reverse(@{$dirorder{$i}})) {
+ if ($env{'form.archive_'.$item} eq 'display') {
+ $outer = $item;
+ last;
+ }
+ }
+ }
+ }
+ my ($errtext,$fatal) =
+ &LONCAPA::map::mapread('/uploaded/'.$docudom.'/'.$docuname.
+ '/'.$folders{$outer}.'.'.
+ $containers{$outer});
+ next if ($fatal);
+ if ((@archdirs > 0) && (grep(/^\Q$i\E$/,@archdirs))) {
+ if ($context eq 'coursedocs') {
+ $mapinner{$i} = time;
+ $folders{$i} = 'default_'.$mapinner{$i};
+ $containers{$i} = 'sequence';
+ my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
+ $folders{$i}.'.'.$containers{$i};
+ my $newidx = &LONCAPA::map::getresidx();
+ $LONCAPA::map::resources[$newidx]=
+ $docstitle.':'.$url.':false:normal:res';
+ push(@LONCAPA::map::order,$newidx);
+ my ($outtext,$errtext) =
+ &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
+ $docuname.'/'.$folders{$outer}.
+ '.'.$containers{$outer},1,1);
+ $newseqid{$i} = $newidx;
+ unless ($errtext) {
+ $result .= ''.&mt('Folder: [_1] added to course',$docstitle).' '."\n";
+ }
+ }
+ } else {
+ if ($context eq 'coursedocs') {
+ my $newidx=&LONCAPA::map::getresidx();
+ my $url = '/uploaded/'.$docudom.'/'.$docuname.'/'.
+ $docstype.'/'.$mapinner{$outer}.'/'.$newidx.'/'.
+ $title;
+ if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
+ mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
+ }
+ if (!-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+ mkdir("$prefix$dir/$docstype/$mapinner{$outer}/$newidx");
+ }
+ if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+ system("mv $prefix$path $prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title");
+ $newdest{$i} = "$prefix$dir/$docstype/$mapinner{$outer}/$newidx";
+ unless ($ishome) {
+ my $fetch = "$newdest{$i}/$title";
+ $fetch =~ s/^\Q$prefix$dir\E//;
+ $prompttofetch{$fetch} = 1;
+ }
+ }
+ $LONCAPA::map::resources[$newidx]=
+ $docstitle.':'.$url.':false:normal:res';
+ push(@LONCAPA::map::order, $newidx);
+ my ($outtext,$errtext)=
+ &LONCAPA::map::storemap('/uploaded/'.$docudom.'/'.
+ $docuname.'/'.$folders{$outer}.
+ '.'.$containers{$outer},1,1);
+ unless ($errtext) {
+ if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx/$title") {
+ $result .= ''.&mt('File: [_1] added to course',$docstitle).' '."\n";
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).' ';
+ }
+ }
+ for (my $i=1; $i<=$numitems; $i++) {
+ next unless ($env{'form.archive_'.$i} eq 'dependency');
+ my $path = $env{'form.archive_content_'.$i};
+ if ($path =~ /^\Q$pathtocheck\E/) {
+ my ($title) = ($path =~ m{/([^/]+)$});
+ $referrer{$i} = $env{'form.archive_dependent_on_'.$i};
+ if ($env{'form.archive_'.$referrer{$i}} eq 'display') {
+ if (ref($dirorder{$i}) eq 'ARRAY') {
+ my ($itemidx,$fullpath,$relpath);
+ if (ref($dirorder{$referrer{$i}}) eq 'ARRAY') {
+ my $container = $dirorder{$referrer{$i}}->[-1];
+ for (my $j=0; $j<@{$dirorder{$i}}; $j++) {
+ if ($dirorder{$i}->[$j] eq $container) {
+ $itemidx = $j;
+ }
+ }
+ }
+ if ($itemidx eq '') {
+ $itemidx = 0;
+ }
+ if (grep(/^\Q$referrer{$i}\E$/,@archdirs)) {
+ if ($mapinner{$referrer{$i}}) {
+ $fullpath = "$prefix$dir/$docstype/$mapinner{$referrer{$i}}";
+ for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+ if (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+ unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+ $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ if (!-e $fullpath) {
+ mkdir($fullpath,0755);
+ }
+ }
+ } else {
+ last;
+ }
+ }
+ }
+ } elsif ($newdest{$referrer{$i}}) {
+ $fullpath = $newdest{$referrer{$i}};
+ for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+ if ($env{'form.archive_'.$dirorder{$i}->[$j]} eq 'discard') {
+ $orphaned{$i} = $env{'form.archive_'.$dirorder{$i}->[$j]};
+ last;
+ } elsif (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+ unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+ $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ if (!-e $fullpath) {
+ mkdir($fullpath,0755);
+ }
+ }
+ } else {
+ last;
+ }
+ }
+ }
+ if ($fullpath ne '') {
+ if (-e "$prefix$path") {
+ system("mv $prefix$path $fullpath/$title");
+ }
+ if (-e "$fullpath/$title") {
+ my $showpath;
+ if ($relpath ne '') {
+ $showpath = "$relpath/$title";
+ } else {
+ $showpath = "/$title";
+ }
+ $result .= ''.&mt('[_1] included as a dependency',$showpath).' '."\n";
+ }
+ unless ($ishome) {
+ my $fetch = "$fullpath/$title";
+ $fetch =~ s/^\Q$prefix$dir\E//;
+ $prompttofetch{$fetch} = 1;
+ }
+ }
+ }
+ } elsif ($env{'form.archive_'.$referrer{$i}} eq 'discard') {
+ $warning .= &mt('[_1] is a dependency of [_2], which was discarded.',
+ $path,$env{'form.archive_content_'.$referrer{$i}}).' ';
+ }
+ } else {
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).' ';
+ }
+ }
+ if (keys(%todelete)) {
+ foreach my $key (keys(%todelete)) {
+ unlink($key);
+ }
+ }
+ if (keys(%todeletedir)) {
+ foreach my $key (keys(%todeletedir)) {
+ rmdir($key);
+ }
+ }
+ foreach my $dir (sort(keys(%is_dir))) {
+ if (($pathtocheck ne '') && ($dir ne '')) {
+ &cleanup_empty_dirs($prefix."$pathtocheck/$dir");
+ }
+ }
+ if ($result ne '') {
+ $output .= ''."\n".
+ $result."\n".
+ ' ';
+ }
+ unless ($ishome) {
+ my $replicationfail;
+ foreach my $item (keys(%prompttofetch)) {
+ my $fetchresult= &Apache::lonnet::reply('fetchuserfile:'.$item,$docuhome);
+ unless ($fetchresult eq 'ok') {
+ $replicationfail .= ''.$item.' '."\n";
+ }
+ }
+ if ($replicationfail) {
+ $output .= ''.
+ &mt('Course home server failed to retrieve:').'
'.
+ $replicationfail.
+ ' ';
+ }
+ }
+ } else {
+ $warning = &mt('No items found in archive.');
+ }
+ if ($error) {
+ $output .= ''.&mt('Not extracted.').' '.
+ $error.'
'."\n";
+ }
+ if ($warning) {
+ $output .= ''.$warning.'
'."\n";
+ }
+ return $output;
+}
+
+sub cleanup_empty_dirs {
+ my ($path) = @_;
+ if (($path ne '') && (-d $path)) {
+ if (opendir(my $dirh,$path)) {
+ my @dircontents = grep(!/^\./,readdir($dirh));
+ my $numitems = 0;
+ foreach my $item (@dircontents) {
+ if (-d "$path/$item") {
+ &cleanup_empty_dirs("$path/$item");
+ if (-e "$path/$item") {
+ $numitems ++;
+ }
+ } else {
+ $numitems ++;
+ }
+ }
+ if ($numitems == 0) {
+ rmdir($path);
+ }
+ closedir($dirh);
+ }
+ }
+ return;
+}
+
+=pod
+
+=item * &get_folder_hierarchy()
+
+Provides hierarchy of names of folders/sub-folders containing the current
+item,
+
+Inputs: 3
+ - $navmap - navmaps object
+
+ - $map - url for map (either the trigger itself, or map containing
+ the resource, which is the trigger).
+
+ - $showitem - 1 => show title for map itself; 0 => do not show.
+
+Outputs: 1 @pathitems - array of folder/subfolder names.
+
+=cut
+
+sub get_folder_hierarchy {
+ my ($navmap,$map,$showitem) = @_;
+ my @pathitems;
+ if (ref($navmap)) {
+ my $mapres = $navmap->getResourceByUrl($map);
+ if (ref($mapres)) {
+ my $pcslist = $mapres->map_hierarchy();
+ if ($pcslist ne '') {
+ my @pcs = split(/,/,$pcslist);
+ foreach my $pc (@pcs) {
+ if ($pc == 1) {
+ push(@pathitems,&mt('Main Content'));
+ } else {
+ my $res = $navmap->getByMapPc($pc);
+ if (ref($res)) {
+ my $title = $res->compTitle();
+ $title =~ s/\W+/_/g;
+ if ($title ne '') {
+ push(@pathitems,$title);
+ }
+ }
+ }
+ }
+ }
+ if ($showitem) {
+ if ($mapres->{ID} eq '0.0') {
+ push(@pathitems,&mt('Main Content'));
+ } else {
+ my $maptitle = $mapres->compTitle();
+ $maptitle =~ s/\W+/_/g;
+ if ($maptitle ne '') {
+ push(@pathitems,$maptitle);
+ }
+ }
+ }
}
}
+ return @pathitems;
}
+=pod
+
+=item * &get_turnedin_filepath()
+
+Determines path in a user's portfolio file for storage of files uploaded
+to a specific essayresponse or dropbox item.
+
+Inputs: 3 required + 1 optional.
+$symb is symb for resource, $uname and $udom are for current user (required).
+$caller is optional (can be "submission", if routine is called when storing
+an upoaded file when "Submit Answer" button was pressed).
+
+Returns array containing $path and $multiresp.
+$path is path in portfolio. $multiresp is 1 if this resource contains more
+than one file upload item. Callers of routine should append partid as a
+subdirectory to $path in cases where $multiresp is 1.
+
+Called by: homework/essayresponse.pm and homework/structuretags.pm
+
+=cut
+
+sub get_turnedin_filepath {
+ my ($symb,$uname,$udom,$caller) = @_;
+ my ($map,$resid,$resurl)=&Apache::lonnet::decode_symb($symb);
+ my $turnindir;
+ my %userhash = &Apache::lonnet::userenvironment($udom,$uname,'turnindir');
+ $turnindir = $userhash{'turnindir'};
+ my ($path,$multiresp);
+ if ($turnindir eq '') {
+ if ($caller eq 'submission') {
+ $turnindir = &mt('turned in');
+ $turnindir =~ s/\W+/_/g;
+ my %newhash = (
+ 'turnindir' => $turnindir,
+ );
+ &Apache::lonnet::put('environment',\%newhash,$udom,$uname);
+ }
+ }
+ if ($turnindir ne '') {
+ $path = '/'.$turnindir.'/';
+ my ($multipart,$turnin,@pathitems);
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (defined($navmap)) {
+ my $mapres = $navmap->getResourceByUrl($map);
+ if (ref($mapres)) {
+ my $pcslist = $mapres->map_hierarchy();
+ if ($pcslist ne '') {
+ foreach my $pc (split(/,/,$pcslist)) {
+ my $res = $navmap->getByMapPc($pc);
+ if (ref($res)) {
+ my $title = $res->compTitle();
+ $title =~ s/\W+/_/g;
+ if ($title ne '') {
+ if (($pc > 1) && (length($title) > 12)) {
+ $title = substr($title,0,12);
+ }
+ push(@pathitems,$title);
+ }
+ }
+ }
+ }
+ my $maptitle = $mapres->compTitle();
+ $maptitle =~ s/\W+/_/g;
+ if ($maptitle ne '') {
+ if (length($maptitle) > 12) {
+ $maptitle = substr($maptitle,0,12);
+ }
+ push(@pathitems,$maptitle);
+ }
+ unless ($env{'request.state'} eq 'construct') {
+ my $res = $navmap->getBySymb($symb);
+ if (ref($res)) {
+ my $partlist = $res->parts();
+ my $totaluploads = 0;
+ if (ref($partlist) eq 'ARRAY') {
+ foreach my $part (@{$partlist}) {
+ my @types = $res->responseType($part);
+ my @ids = $res->responseIds($part);
+ for (my $i=0; $i < scalar(@ids); $i++) {
+ if ($types[$i] eq 'essay') {
+ my $partid = $part.'_'.$ids[$i];
+ if (&Apache::lonnet::EXT("resource.$partid.uploadedfiletypes") ne '') {
+ $totaluploads ++;
+ }
+ }
+ }
+ }
+ if ($totaluploads > 1) {
+ $multiresp = 1;
+ }
+ }
+ }
+ }
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ my $restitle=&Apache::lonnet::gettitle($symb);
+ $restitle =~ s/\W+/_/g;
+ if ($restitle eq '') {
+ $restitle = ($resurl =~ m{/[^/]+$});
+ if ($restitle eq '') {
+ $restitle = time;
+ }
+ }
+ if (length($restitle) > 12) {
+ $restitle = substr($restitle,0,12);
+ }
+ push(@pathitems,$restitle);
+ $path .= join('/',@pathitems);
+ }
+ return ($path,$multiresp);
+}
=pod
@@ -9412,16 +13639,20 @@ sub restore_settings {
=item * &build_recipient_list()
-Build recipient lists for five types of e-mail:
+Build recipient lists for following types of e-mail:
(a) Error Reports, (b) Package Updates, (c) lonstatus warnings/errors
-(d) Help requests, (e) Course requests needing approval, generated by
-lonerrorhandler.pm, CHECKRPMS, loncron, lonsupportreq.pm and
-loncoursequeueadmin.pm respectively.
+(d) Help requests, (e) Course requests needing approval, (f) loncapa
+module change checking, student/employee ID conflict checks, as
+generated by lonerrorhandler.pm, CHECKRPMS, loncron,
+lonsupportreq.pm, loncoursequeueadmin.pm, searchcat.pl respectively.
Inputs:
defmail (scalar - email address of default recipient),
-mailing type (scalar - errormail, packagesmail, or helpdeskmail),
+mailing type (scalar: errormail, packagesmail, helpdeskmail,
+requestsmail, updatesmail, or idconflictsmail).
+
defdom (domain for which to retrieve configuration settings),
+
origmail (scalar - email address of recipient from loncapa.conf,
i.e., predates configuration by DC via domainprefs.pm
@@ -9617,7 +13848,7 @@ sub extract_categories {
=pod
-=item *&recurse_categories()
+=item * &recurse_categories()
Recursively used to generate breadcrumb trails for course categories.
@@ -9688,7 +13919,7 @@ sub recurse_categories {
=pod
-=item *&assign_categories_table()
+=item * &assign_categories_table()
Create a datatable for display of hierarchical categories in a domain,
with checkboxes to allow a course to be categorized.
@@ -9765,7 +13996,7 @@ sub assign_categories_table {
=pod
-=item *&assign_category_rows()
+=item * &assign_category_rows()
Create a datatable row for display of nested categories in a domain,
with checkboxes to allow a course to be categorized,called recursively.
@@ -9799,7 +14030,7 @@ sub assign_category_rows {
if (ref($cats->[$depth]{$parent}) eq 'ARRAY') {
my $numchildren = @{$cats->[$depth]{$parent}};
my $css_class = $itemcount%2?' class="LC_odd_row"':'';
- $text .= '';
+ $text .= '';
for (my $j=0; $j<$numchildren; $j++) {
$name = $cats->[$depth]{$parent}[$j];
$item = &escape($name).':'.&escape($parent).':'.$depth;
@@ -9831,6 +14062,12 @@ sub assign_category_rows {
return $text;
}
+=pod
+
+=back
+
+=cut
+
############################################################
############################################################
@@ -9847,7 +14084,7 @@ sub commit_customrole {
}
sub commit_standardrole {
- my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context) = @_;
+ my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits) = @_;
my ($output,$logmsg,$linefeed);
if ($context eq 'auto') {
$linefeed = "\n";
@@ -9856,7 +14093,7 @@ sub commit_standardrole {
}
if ($three eq 'st') {
my $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end,
- $one,$two,$sec,$context);
+ $one,$two,$sec,$context,$credits);
if (($result =~ /^error/) || ($result eq 'not_in_class') ||
($result eq 'unknown_course') || ($result eq 'refused')) {
$output = $logmsg.' '.&mt('Error: ').$result."\n";
@@ -9887,7 +14124,8 @@ sub commit_standardrole {
}
sub commit_studentrole {
- my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context) = @_;
+ my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,
+ $credits) = @_;
my ($result,$linefeed,$oldsecurl,$newsecurl);
if ($context eq 'auto') {
$linefeed = "\n";
@@ -9934,7 +14172,11 @@ sub commit_studentrole {
}
}
if (($expire_role_result eq 'ok') || ($secchange == 0)) {
- $modify_section_result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,'','',$cid,'',$context);
+ $modify_section_result =
+ &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,
+ undef,undef,undef,$sec,
+ $end,$start,'','',$cid,
+ '',$context,$credits);
if ($modify_section_result =~ /^ok/) {
if ($secchange == 1) {
if ($sec eq '') {
@@ -9956,7 +14198,7 @@ sub commit_studentrole {
}
}
} else {
- if ($secchange) {
+ if ($secchange) {
$$logmsg .= &mt('Error when attempting section change for [_1] from old section "[_2]" to new section: "[_3]" in course [_4] -error:',$uname,$oldsec,$sec,$cid).' '.$modify_section_result.$linefeed;
} else {
$$logmsg .= &mt('Error when attempting to modify role for [_1] for section: "[_2]" in course [_3] -error:',$uname,$sec,$cid).' '.$modify_section_result.$linefeed;
@@ -9965,7 +14207,7 @@ sub commit_studentrole {
$result = $modify_section_result;
} elsif ($secchange == 1) {
if ($oldsec eq '') {
- $$logmsg .= &mt('Error when attempting to expire existing role without a section for [_1] in course [_3] -error: ',$uname,$cid).' '.$expire_role_result.$linefeed;
+ $$logmsg .= &mt('Error when attempting to expire existing role without a section for [_1] in course [_2] -error: ',$uname,$cid).' '.$expire_role_result.$linefeed;
} else {
$$logmsg .= &mt('Error when attempting to expire existing role for [_1] in section [_2] in course [_3] -error: ',$uname,$oldsec,$cid).' '.$expire_role_result.$linefeed;
}
@@ -9991,6 +14233,26 @@ sub commit_studentrole {
return $result;
}
+sub show_role_extent {
+ my ($scope,$context,$role) = @_;
+ $scope =~ s{^/}{};
+ my @courseroles = &Apache::lonuserutils::roles_by_context('course',1);
+ push(@courseroles,'co');
+ my @authorroles = &Apache::lonuserutils::roles_by_context('author');
+ if (($context eq 'course') || (grep(/^\Q$role\E/,@courseroles))) {
+ $scope =~ s{/}{_};
+ return ''.$env{'course.'.$scope.'.description'}.' ';
+ } elsif (($context eq 'author') || (grep(/^\Q$role\E/,@authorroles))) {
+ my ($audom,$auname) = split(/\//,$scope);
+ return &mt('[_1] Author Space',''.
+ &Apache::loncommon::plainname($auname,$audom).' ');
+ } else {
+ $scope =~ s{/$}{};
+ return &mt('Domain: [_1]',''.
+ &Apache::lonnet::domain($scope,'description').' ');
+ }
+}
+
############################################################
############################################################
@@ -10058,7 +14320,7 @@ sub check_clone {
}
sub construct_course {
- my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category) = @_;
+ my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category,$coderef) = @_;
my $outcome;
my $linefeed = ' '."\n";
if ($context eq 'auto') {
@@ -10154,8 +14416,13 @@ sub construct_course {
'pch.users.denied',
'plc.users.denied',
'hidefromcat',
- 'categories'],
+ 'checkforpriv',
+ 'categories',
+ 'internal.uniquecode'],
$$crsudom,$$crsunum);
+ if ($args->{'textbook'}) {
+ $cenv{'internal.textbook'} = $args->{'textbook'};
+ }
}
#
@@ -10183,6 +14450,9 @@ sub construct_course {
} else {
$cenv{'internal.courseowner'} = $args->{'curruser'};
}
+ if ($args->{'defaultcredits'}) {
+ $cenv{'internal.defaultcredits'} = $args->{'defaultcredits'};
+ }
my @badclasses = (); # Used to accumulate sections/crosslistings that did not pass classlist access check for course owner.
if ($args->{'crssections'}) {
$cenv{'internal.sectionnums'} = '';
@@ -10207,6 +14477,11 @@ sub construct_course {
# do not hide course coordinator from staff listing,
# even if privileged
$cenv{'nothideprivileged'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
+# add course coordinator's domain to domains to check for privileged users
+# if different to course domain
+ if ($$crsudom ne $args->{'ccdomain'}) {
+ $cenv{'checkforpriv'} = $args->{'ccdomain'};
+ }
# add crosslistings
if ($args->{'crsxlist'}) {
$cenv{'internal.crosslistings'}='';
@@ -10331,6 +14606,25 @@ sub construct_course {
}
}
+#
+# generate and store uniquecode (available to course requester), if course should have one.
+#
+ if ($args->{'uniquecode'}) {
+ my ($code,$error) = &make_unique_code($$crsudom,$$crsunum);
+ if ($code) {
+ $cenv{'internal.uniquecode'} = $code;
+ my %crsinfo =
+ &Apache::lonnet::courseiddump($$crsudom,'.',1,'.','.',$$crsunum,undef,undef,'.');
+ if (ref($crsinfo{$$crsudom.'_'.$$crsunum}) eq 'HASH') {
+ $crsinfo{$$crsudom.'_'.$$crsunum}{'uniquecode'} = $code;
+ my $putres = &Apache::lonnet::courseidput($$crsudom,\%crsinfo,$crsuhome,'notime');
+ }
+ if (ref($coderef)) {
+ $$coderef = $code;
+ }
+ }
+ }
+
if ($args->{'disresdis'}) {
$cenv{'pch.roles.denied'}='st';
}
@@ -10399,6 +14693,60 @@ sub construct_course {
return (1,$outcome);
}
+sub make_unique_code {
+ my ($cdom,$cnum) = @_;
+ # get lock on uniquecodes db
+ my $lockhash = {
+ $cnum."\0".'uniquecodes' => $env{'user.name'}.
+ ':'.$env{'user.domain'},
+ };
+ my $tries = 0;
+ my $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+ my ($code,$error);
+
+ while (($gotlock ne 'ok') && ($tries<3)) {
+ $tries ++;
+ sleep 1;
+ $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+ }
+ if ($gotlock eq 'ok') {
+ my %currcodes = &Apache::lonnet::dump_dom('uniquecodes',$cdom);
+ my $gotcode;
+ my $attempts = 0;
+ while ((!$gotcode) && ($attempts < 100)) {
+ $code = &generate_code();
+ if (!exists($currcodes{$code})) {
+ $gotcode = 1;
+ unless (&Apache::lonnet::newput_dom('uniquecodes',{ $code => $cnum },$cdom) eq 'ok') {
+ $error = 'nostore';
+ }
+ }
+ $attempts ++;
+ }
+ my @del_lock = ($cnum."\0".'uniquecodes');
+ my $dellockoutcome = &Apache::lonnet::del_dom('uniquecodes',\@del_lock,$cdom);
+ } else {
+ $error = 'nolock';
+ }
+ return ($code,$error);
+}
+
+sub generate_code {
+ my $code;
+ my @letts = qw(B C D G H J K M N P Q R S T V W X Z);
+ for (my $i=0; $i<6; $i++) {
+ my $lettnum = int (rand 2);
+ my $item = '';
+ if ($lettnum) {
+ $item = $letts[int( rand(18) )];
+ } else {
+ $item = 1+int( rand(8) );
+ }
+ $code .= $item;
+ }
+ return $code;
+}
+
############################################################
############################################################
@@ -10426,11 +14774,12 @@ sub group_term {
}
sub course_types {
- my @types = ('official','unofficial','community');
+ my @types = ('official','unofficial','community','textbook');
my %typename = (
official => 'Official course',
unofficial => 'Unofficial course',
community => 'Community',
+ textbook => 'Textbook course',
);
return (\@types,\%typename);
}
@@ -10491,7 +14840,7 @@ sub escape_url {
my ($url) = @_;
my @urlslices = split(/\//, $url,-1);
my $lastitem = &escape(pop(@urlslices));
- return join('/',@urlslices).'/'.$lastitem;
+ return &HTML::Entities::encode(join('/',@urlslices),"'").'/'.$lastitem;
}
sub compare_arrays {
@@ -10519,7 +14868,7 @@ sub init_user_environment {
# See if old ID present, if so, remove
- my ($filename,$cookie,$userroles);
+ my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
my $now=time;
if ($public) {
@@ -10549,6 +14898,17 @@ sub init_user_environment {
}
}
closedir(DIR);
+# If there is a undeleted lockfile for the user's paste buffer remove it.
+ my $namespace = 'nohist_courseeditor';
+ my $lockingkey = 'paste'."\0".'locked_num';
+ my %lockhash = &Apache::lonnet::get($namespace,[$lockingkey],
+ $domain,$username);
+ if (exists($lockhash{$lockingkey})) {
+ my $delresult = &Apache::lonnet::del($namespace,[$lockingkey],$domain,$username);
+ unless ($delresult eq 'ok') {
+ &Apache::lonnet::logthis("Failed to delete paste buffer locking key in $namespace for ".$username.":".$domain." Result was: $delresult");
+ }
+ }
}
# Give them a new cookie
my $id = ($args->{'robot'} ? 'robot'.$args->{'robot'}
@@ -10557,12 +14917,13 @@ sub init_user_environment {
# Initialize roles
- $userroles=&Apache::lonnet::rolesinit($domain,$username,$authhost);
+ ($userroles,$firstaccenv,$timerintenv) =
+ &Apache::lonnet::rolesinit($domain,$username,$authhost);
}
# ------------------------------------ Check browser type and MathML capability
- my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
- $clientunicode,$clientos) = &decode_user_agent($r);
+ my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,$clientunicode,
+ $clientos,$clientmobile,$clientinfo,$clientosversion) = &decode_user_agent($r);
# ------------------------------------------------------------- Get environment
@@ -10593,6 +14954,9 @@ sub init_user_environment {
"browser.mathml" => $clientmathml,
"browser.unicode" => $clientunicode,
"browser.os" => $clientos,
+ "browser.mobile" => $clientmobile,
+ "browser.info" => $clientinfo,
+ "browser.osversion" => $clientosversion,
"server.domain" => $Apache::lonnet::perlvar{'lonDefDomain'},
"request.course.fn" => '',
"request.course.uri" => '',
@@ -10612,24 +14976,58 @@ sub init_user_environment {
$env{'browser.interface'}=$form->{'interface'};
}
- foreach my $tool ('aboutme','blog','portfolio') {
+ if ($form->{'iptoken'}) {
+ my $lonhost = $r->dir_config('lonHostID');
+ $initial_env{"user.noloadbalance"} = $lonhost;
+ $env{'user.noloadbalance'} = $lonhost;
+ }
+
+ my %is_adv = ( is_adv => $env{'user.adv'} );
+ my %domdef;
+ unless ($domain eq 'public') {
+ %domdef = &Apache::lonnet::get_domain_defaults($domain);
+ }
+
+ foreach my $tool ('aboutme','blog','webdav','portfolio') {
$userenv{'availabletools.'.$tool} =
- &Apache::lonnet::usertools_access($username,$domain,$tool,'reload');
+ &Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
+ undef,\%userenv,\%domdef,\%is_adv);
}
- foreach my $crstype ('official','unofficial','community') {
+ foreach my $crstype ('official','unofficial','community','textbook') {
$userenv{'canrequest.'.$crstype} =
&Apache::lonnet::usertools_access($username,$domain,$crstype,
- 'reload','requestcourses');
+ 'reload','requestcourses',
+ \%userenv,\%domdef,\%is_adv);
+ }
+
+ $userenv{'canrequest.author'} =
+ &Apache::lonnet::usertools_access($username,$domain,'requestauthor',
+ 'reload','requestauthor',
+ \%userenv,\%domdef,\%is_adv);
+ my %reqauthor = &Apache::lonnet::get('requestauthor',['author_status','author'],
+ $domain,$username);
+ my $reqstatus = $reqauthor{'author_status'};
+ if ($reqstatus eq 'approval' || $reqstatus eq 'approved') {
+ if (ref($reqauthor{'author'}) eq 'HASH') {
+ $userenv{'requestauthorqueued'} = $reqstatus.':'.
+ $reqauthor{'author'}{'timestamp'};
+ }
}
$env{'user.environment'} = "$lonids/$cookie.id";
-
+
if (tie(my %disk_env,'GDBM_File',"$lonids/$cookie.id",
&GDBM_WRCREAT(),0640)) {
&_add_to_env(\%disk_env,\%initial_env);
&_add_to_env(\%disk_env,\%userenv,'environment.');
&_add_to_env(\%disk_env,$userroles);
+ if (ref($firstaccenv) eq 'HASH') {
+ &_add_to_env(\%disk_env,$firstaccenv);
+ }
+ if (ref($timerintenv) eq 'HASH') {
+ &_add_to_env(\%disk_env,$timerintenv);
+ }
if (ref($args->{'extra_env'})) {
&_add_to_env(\%disk_env,$args->{'extra_env'});
}
@@ -10665,7 +15063,9 @@ sub get_symb {
my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
if ($symb eq '') {
if (!$silent) {
- $request->print("Unable to handle ambiguous references:$url:.");
+ if (ref($request)) {
+ $request->print("Unable to handle ambiguous references:$url:.");
+ }
return ();
}
}
@@ -10699,12 +15099,1175 @@ sub clean_symb {
return ($symb,$enc);
}
+############################################################
+############################################################
+
+=pod
+
+=head1 Routines for building display used to search for courses
+
+
+=over 4
+
+=item * &build_filters()
+
+Create markup for a table used to set filters to use when selecting
+courses in a domain. Used by lonpickcourse.pm, lonmodifycourse.pm
+and quotacheck.pl
+
+
+Inputs:
+
+filterlist - anonymous array of fields to include as potential filters
+
+crstype - course type
+
+roleelement - fifth arg in selectcourse_link() populates fifth arg in javascript: opencrsbrowser() function, used
+ to pop-open a course selector (will contain "extra element").
+
+multelement - if multiple course selections will be allowed, this will be a hidden form element: name: multiple; value: 1
+
+filter - anonymous hash of criteria and their values
+
+action - form action
+
+numfiltersref - ref to scalar (count of number of elements in institutional codes -- e.g., 4 for year, semester, department, and number)
+
+caller - caller context (e.g., set to 'modifycourse' when routine is called from lonmodifycourse.pm)
+
+cloneruname - username of owner of new course who wants to clone
+
+clonerudom - domain of owner of new course who wants to clone
+
+typeelem - text to use for left column in row containing course type (i.e., Course, Community or Course/Community)
+
+codetitlesref - reference to array of titles of components in institutional codes (official courses)
+
+codedom - domain
+
+formname - value of form element named "form".
+
+fixeddom - domain, if fixed.
+
+prevphase - value to assign to form element named "phase" when going back to the previous screen
+
+cnameelement - name of form element in form on opener page which will receive title of selected course
+
+cnumelement - name of form element in form on opener page which will receive courseID of selected course
+
+cdomelement - name of form element in form on opener page which will receive domain of selected course
+
+setroles - includes access constraint identifier when setting a roles-based condition for acces to a portfolio file
+
+clonetext - hidden form elements containing list of courses cloneable by intended course owner when DC creates a course
+
+clonewarning - warning message about missing information for intended course owner when DC creates a course
+
+
+Returns: $output - HTML for display of search criteria, and hidden form elements.
+
+
+Side Effects: None
+
+=cut
+
+# ---------------------------------------------- search for courses based on last activity etc.
+
+sub build_filters {
+ my ($filterlist,$crstype,$roleelement,$multelement,$filter,$action,
+ $numtitlesref,$caller,$cloneruname,$clonerudom,$typeelement,
+ $codetitlesref,$codedom,$formname,$fixeddom,$prevphase,
+ $cnameelement,$cnumelement,$cdomelement,$setroles,
+ $clonetext,$clonewarning) = @_;
+ my ($list,$jscript);
+ my $onchange = 'javascript:updateFilters(this)';
+ my ($domainselectform,$sincefilterform,$createdfilterform,
+ $ownerdomselectform,$persondomselectform,$instcodeform,
+ $typeselectform,$instcodetitle);
+ if ($formname eq '') {
+ $formname = $caller;
+ }
+ foreach my $item (@{$filterlist}) {
+ unless (($item eq 'descriptfilter') || ($item eq 'instcodefilter') ||
+ ($item eq 'sincefilter') || ($item eq 'createdfilter')) {
+ if ($item eq 'domainfilter') {
+ $filter->{$item} = &LONCAPA::clean_domain($filter->{$item});
+ } elsif ($item eq 'coursefilter') {
+ $filter->{$item} = &LONCAPA::clean_courseid($filter->{$item});
+ } elsif ($item eq 'ownerfilter') {
+ $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+ } elsif ($item eq 'ownerdomfilter') {
+ $filter->{'ownerdomfilter'} =
+ &LONCAPA::clean_domain($filter->{$item});
+ $ownerdomselectform = &select_dom_form($filter->{'ownerdomfilter'},
+ 'ownerdomfilter',1);
+ } elsif ($item eq 'personfilter') {
+ $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+ } elsif ($item eq 'persondomfilter') {
+ $persondomselectform = &select_dom_form($filter->{'persondomfilter'},
+ 'persondomfilter',1);
+ } else {
+ $filter->{$item} =~ s/\W//g;
+ }
+ if (!$filter->{$item}) {
+ $filter->{$item} = '';
+ }
+ }
+ if ($item eq 'domainfilter') {
+ my $allow_blank = 1;
+ if ($formname eq 'portform') {
+ $allow_blank=0;
+ } elsif ($formname eq 'studentform') {
+ $allow_blank=0;
+ }
+ if ($fixeddom) {
+ $domainselectform = ' '.
+ &Apache::lonnet::domain($codedom,'description');
+ } else {
+ $domainselectform = &select_dom_form($filter->{$item},
+ 'domainfilter',
+ $allow_blank,'',$onchange);
+ }
+ } else {
+ $list->{$item} = &HTML::Entities::encode($filter->{$item},'<>&"');
+ }
+ }
+
+ # last course activity filter and selection
+ $sincefilterform = &timebased_select_form('sincefilter',$filter);
+
+ # course created filter and selection
+ if (exists($filter->{'createdfilter'})) {
+ $createdfilterform = &timebased_select_form('createdfilter',$filter);
+ }
+
+ my %lt = &Apache::lonlocal::texthash(
+ 'cac' => "$crstype Activity",
+ 'ccr' => "$crstype Created",
+ 'cde' => "$crstype Title",
+ 'cdo' => "$crstype Domain",
+ 'ins' => 'Institutional Code',
+ 'inc' => 'Institutional Categorization',
+ 'cow' => "$crstype Owner/Co-owner",
+ 'cop' => "$crstype Personnel Includes",
+ 'cog' => 'Type',
+ );
+
+ if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+ my $typeval = 'Course';
+ if ($crstype eq 'Community') {
+ $typeval = 'Community';
+ }
+ $typeselectform = ' ';
+ } else {
+ $typeselectform = '".&mt($posstype)."\n";
+ }
+ $typeselectform.=" ";
+ }
+
+ my ($cloneableonlyform,$cloneabletitle);
+ if (exists($filter->{'cloneableonly'})) {
+ my $cloneableon = '';
+ my $cloneableoff = ' checked="checked"';
+ if ($filter->{'cloneableonly'}) {
+ $cloneableon = $cloneableoff;
+ $cloneableoff = '';
+ }
+ $cloneableonlyform = ' '.&mt('Required').' '.(' 'x3).' '.&mt('No restriction').' ';
+ if ($formname eq 'ccrs') {
+ $cloneabletitle = &mt('Cloneable for [_1]',$cloneruname.':'.$clonerudom);
+ } else {
+ $cloneabletitle = &mt('Cloneable by you');
+ }
+ }
+ my $officialjs;
+ if ($crstype eq 'Course') {
+ if (exists($filter->{'instcodefilter'})) {
+# if (($fixeddom) || ($formname eq 'requestcrs') ||
+# ($formname eq 'modifycourse') || ($formname eq 'filterpicker')) {
+ if ($codedom) {
+ $officialjs = 1;
+ ($instcodeform,$jscript,$$numtitlesref) =
+ &Apache::courseclassifier::instcode_selectors($codedom,'filterpicker',
+ $officialjs,$codetitlesref);
+ if ($jscript) {
+ $jscript = ''."\n";
+ }
+ }
+ if ($instcodeform eq '') {
+ $instcodeform =
+ ' ';
+ $instcodetitle = $lt{'ins'};
+ } else {
+ $instcodetitle = $lt{'inc'};
+ }
+ if ($fixeddom) {
+ $instcodetitle .= ' ('.$codedom.')';
+ }
+ }
+ }
+ my $output = qq|
+'."\n".' '."\n";
+ return $jscript.$clonewarning.$output;
+}
+
+=pod
+
+=item * &timebased_select_form()
+
+Create markup for a dropdown list used to select a time-based
+filter e.g., Course Activity, Course Created, when searching for courses
+or communities
+
+Inputs:
+
+item - name of form element (sincefilter or createdfilter)
+
+filter - anonymous hash of criteria and their values
+
+Returns: HTML for a select box contained a blank, then six time selections,
+ with value set in incoming form variables currently selected.
+
+Side Effects: None
+
+=cut
+
+sub timebased_select_form {
+ my ($item,$filter) = @_;
+ if (ref($filter) eq 'HASH') {
+ $filter->{$item} =~ s/[^\d-]//g;
+ if (!$filter->{$item}) { $filter->{$item}=-1; }
+ return &select_form(
+ $filter->{$item},
+ $item,
+ { '-1' => '',
+ '86400' => &mt('today'),
+ '604800' => &mt('last week'),
+ '2592000' => &mt('last month'),
+ '7776000' => &mt('last three months'),
+ '15552000' => &mt('last six months'),
+ '31104000' => &mt('last year'),
+ 'select_form_order' =>
+ ['-1','86400','604800','2592000','7776000',
+ '15552000','31104000']});
+ }
+}
+
+=pod
+
+=item * &js_changer()
+
+Create script tag containing Javascript used to submit course search form
+when course type or domain is changed, and also to hide 'Searching ...' on
+page load completion for page showing search result.
+
+Inputs: None
+
+Returns: markup containing updateFilters() and hideSearching() javascript functions.
+
+Side Effects: None
+
+=cut
+
+sub js_changer {
+ return <
+//
+
+
+ENDJS
+}
+
+=pod
+
+=item * &search_courses()
+
+Process selected filters form course search form and pass to lonnet::courseiddump
+to retrieve a hash for which keys are courseIDs which match the selected filters.
+
+Inputs:
+
+dom - domain being searched
+
+type - course type ('Course' or 'Community' or '.' if any).
+
+filter - anonymous hash of criteria and their values
+
+numtitles - for institutional codes - number of categories
+
+cloneruname - optional username of new course owner
+
+clonerudom - optional domain of new course owner
+
+domcloner - Optional "domcloner" flag; has value=1 if user has ccc priv in domain being filtered by,
+ (used when DC is using course creation form)
+
+codetitles - reference to array of titles of components in institutional codes (official courses).
+
+
+Returns: %courses - hash of courses satisfying search criteria, keys = course IDs, values are corresponding colon-separated escaped description, institutional code, owner and type.
+
+
+Side Effects: None
+
+=cut
+
+
+sub search_courses {
+ my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles) = @_;
+ my (%courses,%showcourses,$cloner);
+ if (($filter->{'ownerfilter'} ne '') ||
+ ($filter->{'ownerdomfilter'} ne '')) {
+ $filter->{'combownerfilter'} = $filter->{'ownerfilter'}.':'.
+ $filter->{'ownerdomfilter'};
+ }
+ foreach my $item ('descriptfilter','coursefilter','combownerfilter') {
+ if (!$filter->{$item}) {
+ $filter->{$item}='.';
+ }
+ }
+ my $now = time;
+ my $timefilter =
+ ($filter->{'sincefilter'}==-1?1:$now-$filter->{'sincefilter'});
+ my ($createdbefore,$createdafter);
+ if (($filter->{'createdfilter'} ne '') && ($filter->{'createdfilter'} !=-1)) {
+ $createdbefore = $now;
+ $createdafter = $now-$filter->{'createdfilter'};
+ }
+ my ($instcodefilter,$regexpok);
+ if ($numtitles) {
+ if ($env{'form.official'} eq 'on') {
+ $instcodefilter =
+ &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+ $regexpok = 1;
+ } elsif ($env{'form.official'} eq 'off') {
+ $instcodefilter = &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles);
+ unless ($instcodefilter eq '') {
+ $regexpok = -1;
+ }
+ }
+ } else {
+ $instcodefilter = $filter->{'instcodefilter'};
+ }
+ if ($instcodefilter eq '') { $instcodefilter = '.'; }
+ if ($type eq '') { $type = '.'; }
+
+ if (($clonerudom ne '') && ($cloneruname ne '')) {
+ $cloner = $cloneruname.':'.$clonerudom;
+ }
+ %courses = &Apache::lonnet::courseiddump($dom,
+ $filter->{'descriptfilter'},
+ $timefilter,
+ $instcodefilter,
+ $filter->{'combownerfilter'},
+ $filter->{'coursefilter'},
+ undef,undef,$type,$regexpok,undef,undef,
+ undef,undef,$cloner,$env{'form.cc_clone'},
+ $filter->{'cloneableonly'},
+ $createdbefore,$createdafter,undef,
+ $domcloner);
+ if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) {
+ my $ccrole;
+ if ($type eq 'Community') {
+ $ccrole = 'co';
+ } else {
+ $ccrole = 'cc';
+ }
+ my %rolehash = &Apache::lonnet::get_my_roles($filter->{'personfilter'},
+ $filter->{'persondomfilter'},
+ 'userroles',undef,
+ [$ccrole,'in','ad','ep','ta','cr'],
+ $dom);
+ foreach my $role (keys(%rolehash)) {
+ my ($cnum,$cdom,$courserole) = split(':',$role);
+ my $cid = $cdom.'_'.$cnum;
+ if (exists($courses{$cid})) {
+ if (ref($courses{$cid}) eq 'HASH') {
+ if (ref($courses{$cid}{roles}) eq 'ARRAY') {
+ if (!grep(/^\Q$courserole\E$/,@{$courses{$cid}{roles}})) {
+ push (@{$courses{$cid}{roles}},$courserole);
+ }
+ } else {
+ $courses{$cid}{roles} = [$courserole];
+ }
+ $showcourses{$cid} = $courses{$cid};
+ }
+ }
+ }
+ %courses = %showcourses;
+ }
+ return %courses;
+}
+
=pod
=back
+=head1 Routines for version requirements for current course.
+
+=over 4
+
+=item * &check_release_required()
+
+Compares required LON-CAPA version with version on server, and
+if required version is newer looks for a server with the required version.
+
+Looks first at servers in user's owen domain; if none suitable, looks at
+servers in course's domain are permitted to host sessions for user's domain.
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$courseid - Course ID of current course
+
+$rolecode - User's current role in course (for switchserver query string).
+
+$required - LON-CAPA version needed by course (format: Major.Minor).
+
+
+Returns:
+
+$switchserver - query string tp append to /adm/switchserver call (if
+ current server's LON-CAPA version is too old.
+
+$warning - Message is displayed if no suitable server could be found.
+
+=cut
+
+sub check_release_required {
+ my ($loncaparev,$courseid,$rolecode,$required) = @_;
+ my ($switchserver,$warning);
+ if ($required ne '') {
+ my ($reqdmajor,$reqdminor) = ($required =~ /^(\d+)\.(\d+)$/);
+ my ($major,$minor) = ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+ if ($reqdmajor ne '' && $reqdminor ne '') {
+ my $otherserver;
+ if (($major eq '' && $minor eq '') ||
+ (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)))) {
+ my ($userdomserver) = &Apache::lonnet::choose_server($env{'user.domain'},undef,$required,1);
+ my $switchlcrev =
+ &Apache::lonnet::get_server_loncaparev($env{'user.domain'},
+ $userdomserver);
+ my ($swmajor,$swminor) = ($switchlcrev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/);
+ if (($swmajor eq '' && $swminor eq '') || ($reqdmajor > $swmajor) ||
+ (($reqdmajor == $swmajor) && ($reqdminor > $swminor))) {
+ my $cdom = $env{'course.'.$courseid.'.domain'};
+ if ($cdom ne $env{'user.domain'}) {
+ my ($coursedomserver,$coursehostname) = &Apache::lonnet::choose_server($cdom,undef,$required,1);
+ my $serverhomeID = &Apache::lonnet::get_server_homeID($coursehostname);
+ my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+ my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom);
+ my %udomdefaults = &Apache::lonnet::get_domain_defaults($env{'user.domain'});
+ my $remoterev = &Apache::lonnet::get_server_loncaparev($serverhomedom,$coursedomserver);
+ my $canhost =
+ &Apache::lonnet::can_host_session($env{'user.domain'},
+ $coursedomserver,
+ $remoterev,
+ $udomdefaults{'remotesessions'},
+ $defdomdefaults{'hostedsessions'});
+
+ if ($canhost) {
+ $otherserver = $coursedomserver;
+ } else {
+ $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).' '. &mt("No suitable server could be found amongst servers in either your own domain or in the course's domain.");
+ }
+ } else {
+ $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).' '.&mt("No suitable server could be found amongst servers in your own domain (which is also the course's domain).");
+ }
+ } else {
+ $otherserver = $userdomserver;
+ }
+ }
+ if ($otherserver ne '') {
+ $switchserver = 'otherserver='.$otherserver.'&role='.$rolecode;
+ }
+ }
+ }
+ return ($switchserver,$warning);
+}
+
+=pod
+
+=item * &check_release_result()
+
+Inputs:
+
+$switchwarning - Warning message if no suitable server found to host session.
+
+$switchserver - query string to append to /adm/switchserver containing lonHostID
+ and current role.
+
+Returns: HTML to display with information about requirement to switch server.
+ Either displaying warning with link to Roles/Courses screen or
+ display link to switchserver.
+
=cut
+sub check_release_result {
+ my ($switchwarning,$switchserver) = @_;
+ my $output = &start_page('Selected course unavailable on this server').
+ '';
+ if ($switchwarning) {
+ $output .= $switchwarning.'';
+ if (&show_course()) {
+ $output .= &mt('Display courses');
+ } else {
+ $output .= &mt('Display roles');
+ }
+ $output .= ' ';
+ } elsif ($switchserver) {
+ $output .= &mt('This course requires a newer version of LON-CAPA than is installed on this server.').
+ ' '.
+ ''.
+ &mt('Switch Server').
+ ' ';
+ }
+ $output .= '
'.&end_page();
+ return $output;
+}
+
+=pod
+
+=item * &needs_coursereinit()
+
+Determine if course contents stored for user's session needs to be
+refreshed, because content has changed since "Big Hash" last tied.
+
+Check for change is made if time last checked is more than 10 minutes ago
+(by default).
+
+Inputs:
+
+$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp)
+
+$interval (optional) - Time which may elapse (in s) between last check for content
+ change in current course. (default: 600 s).
+
+Returns: an array; first element is:
+
+=over 4
+
+'switch' - if content updates mean user's session
+ needs to be switched to a server running a newer LON-CAPA version
+
+'update' - if course session needs to be refreshed (i.e., Big Hash needs to be reloaded)
+ on current server hosting user's session
+
+'' - if no action required.
+
+=back
+
+If first item element is 'switch':
+
+second item is $switchwarning - Warning message if no suitable server found to host session.
+
+third item is $switchserver - query string to append to /adm/switchserver containing lonHostID
+ and current role.
+
+otherwise: no other elements returned.
+
+=back
+
+=cut
+
+sub needs_coursereinit {
+ my ($loncaparev,$interval) = @_;
+ return() unless ($env{'request.course.id'} && $env{'request.course.tied'});
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $now = time;
+ if ($interval eq '') {
+ $interval = 600;
+ }
+ if (($now-$env{'request.course.timechecked'})>$interval) {
+ my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
+ &Apache::lonnet::appenv({'request.course.timechecked'=>$now});
+ if ($lastchange > $env{'request.course.tied'}) {
+ my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
+ if ($curr_reqd_hash{'internal.releaserequired'} ne '') {
+ my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'};
+ if ($curr_reqd_hash{'internal.releaserequired'} ne $required) {
+ &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' =>
+ $curr_reqd_hash{'internal.releaserequired'}});
+ my ($switchserver,$switchwarning) =
+ &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'},
+ $curr_reqd_hash{'internal.releaserequired'});
+ if ($switchwarning ne '' || $switchserver ne '') {
+ return ('switch',$switchwarning,$switchserver);
+ }
+ }
+ }
+ return ('update');
+ }
+ }
+ return ();
+}
+
+sub update_content_constraints {
+ my ($cdom,$cnum,$chome,$cid) = @_;
+ my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired');
+ my ($reqdmajor,$reqdminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'});
+ my %checkresponsetypes;
+ foreach my $key (keys(%Apache::lonnet::needsrelease)) {
+ my ($item,$name,$value) = split(/:/,$key);
+ if ($item eq 'resourcetag') {
+ if ($name eq 'responsetype') {
+ $checkresponsetypes{$value} = $Apache::lonnet::needsrelease{$key}
+ }
+ }
+ }
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (defined($navmap)) {
+ my %allresponses;
+ foreach my $res ($navmap->retrieveResources(undef,sub { $_[0]->is_problem() },1,0)) {
+ my %responses = $res->responseTypes();
+ foreach my $key (keys(%responses)) {
+ next unless(exists($checkresponsetypes{$key}));
+ $allresponses{$key} += $responses{$key};
+ }
+ }
+ foreach my $key (keys(%allresponses)) {
+ my ($major,$minor) = split(/\./,$checkresponsetypes{$key});
+ if (($major > $reqdmajor) || ($major == $reqdmajor && $minor > $reqdminor)) {
+ ($reqdmajor,$reqdminor) = ($major,$minor);
+ }
+ }
+ undef($navmap);
+ }
+ unless (($reqdmajor eq '') && ($reqdminor eq '')) {
+ &Apache::lonnet::update_released_required($reqdmajor.'.'.$reqdminor,$cdom,$cnum,$chome,$cid);
+ }
+ return;
+}
+
+sub allmaps_incourse {
+ my ($cdom,$cnum,$chome,$cid) = @_;
+ if ($cdom eq '' || $cnum eq '' || $chome eq '' || $cid eq '') {
+ $cid = $env{'request.course.id'};
+ $cdom = $env{'course.'.$cid.'.domain'};
+ $cnum = $env{'course.'.$cid.'.num'};
+ $chome = $env{'course.'.$cid.'.home'};
+ }
+ my %allmaps = ();
+ my $lastchange =
+ &Apache::lonnet::get_coursechange($cdom,$cnum);
+ if ($lastchange > $env{'request.course.tied'}) {
+ my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum");
+ unless ($ferr) {
+ &update_content_constraints($cdom,$cnum,$chome,$cid);
+ }
+ }
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (defined($navmap)) {
+ foreach my $res ($navmap->retrieveResources(undef,sub { $_[0]->is_map() },1,0,1)) {
+ $allmaps{$res->src()} = 1;
+ }
+ }
+ return \%allmaps;
+}
+
+sub parse_supplemental_title {
+ my ($title) = @_;
+
+ my ($foldertitle,$renametitle);
+ if ($title =~ /&&&/) {
+ $title = &HTML::Entites::decode($title);
+ }
+ if ($title =~ m/^(\d+)___&&&___($match_username)___&&&___($match_domain)___&&&___(.*)$/) {
+ $renametitle=$4;
+ my ($time,$uname,$udom) = ($1,$2,$3);
+ $foldertitle=&Apache::lontexconvert::msgtexconverted($4);
+ my $name = &plainname($uname,$udom);
+ $name = &HTML::Entities::encode($name,'"<>&\'');
+ $renametitle = &HTML::Entities::encode($renametitle,'"<>&\'');
+ $title=''.&Apache::lonlocal::locallocaltime($time).' '.
+ $name.': '.$foldertitle;
+ }
+ if (wantarray) {
+ return ($title,$foldertitle,$renametitle);
+ }
+ return $title;
+}
+
+sub recurse_supplemental {
+ my ($cnum,$cdom,$suppmap,$numfiles,$errors) = @_;
+ if ($suppmap) {
+ my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap);
+ if ($fatal) {
+ $errors ++;
+ } else {
+ if ($#LONCAPA::map::resources > 0) {
+ foreach my $res (@LONCAPA::map::resources) {
+ my ($title,$src,$ext,$type,$status)=split(/\:/,$res);
+ if (($src ne '') && ($status eq 'res')) {
+ if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
+ ($numfiles,$errors) = &recurse_supplemental($cnum,$cdom,$1,$numfiles,$errors);
+ } else {
+ $numfiles ++;
+ }
+ }
+ }
+ }
+ }
+ }
+ return ($numfiles,$errors);
+}
+
+sub symb_to_docspath {
+ my ($symb) = @_;
+ return unless ($symb);
+ my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb);
+ if ($resurl=~/\.(sequence|page)$/) {
+ $mapurl=$resurl;
+ } elsif ($resurl eq 'adm/navmaps') {
+ $mapurl=$env{'course.'.$env{'request.course.id'}.'.url'};
+ }
+ my $mapresobj;
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (ref($navmap)) {
+ $mapresobj = $navmap->getResourceByUrl($mapurl);
+ }
+ $mapurl=~s{^.*/([^/]+)\.(\w+)$}{$1};
+ my $type=$2;
+ my $path;
+ if (ref($mapresobj)) {
+ my $pcslist = $mapresobj->map_hierarchy();
+ if ($pcslist ne '') {
+ foreach my $pc (split(/,/,$pcslist)) {
+ next if ($pc <= 1);
+ my $res = $navmap->getByMapPc($pc);
+ if (ref($res)) {
+ my $thisurl = $res->src();
+ $thisurl=~s{^.*/([^/]+)\.\w+$}{$1};
+ my $thistitle = $res->title();
+ $path .= '&'.
+ &Apache::lonhtmlcommon::entity_encode($thisurl).'&'.
+ &escape($thistitle).
+ ':'.$res->randompick().
+ ':'.$res->randomout().
+ ':'.$res->encrypted().
+ ':'.$res->randomorder().
+ ':'.$res->is_page();
+ }
+ }
+ }
+ $path =~ s/^\&//;
+ my $maptitle = $mapresobj->title();
+ if ($mapurl eq 'default') {
+ $maptitle = 'Main Content';
+ }
+ $path .= (($path ne '')? '&' : '').
+ &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
+ &escape($maptitle).
+ ':'.$mapresobj->randompick().
+ ':'.$mapresobj->randomout().
+ ':'.$mapresobj->encrypted().
+ ':'.$mapresobj->randomorder().
+ ':'.$mapresobj->is_page();
+ } else {
+ my $maptitle = &Apache::lonnet::gettitle($mapurl);
+ my $ispage = (($type eq 'page')? 1 : '');
+ if ($mapurl eq 'default') {
+ $maptitle = 'Main Content';
+ }
+ $path = &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
+ &escape($maptitle).':::::'.$ispage;
+ }
+ unless ($mapurl eq 'default') {
+ $path = 'default&'.
+ &escape('Main Content').
+ ':::::&'.$path;
+ }
+ return $path;
+}
+
+sub captcha_display {
+ my ($context,$lonhost) = @_;
+ my ($output,$error);
+ my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ if ($captcha eq 'original') {
+ $output = &create_captcha();
+ unless ($output) {
+ $error = 'captcha';
+ }
+ } elsif ($captcha eq 'recaptcha') {
+ $output = &create_recaptcha($pubkey);
+ unless ($output) {
+ $error = 'recaptcha';
+ }
+ }
+ return ($output,$error,$captcha);
+}
+
+sub captcha_response {
+ my ($context,$lonhost) = @_;
+ my ($captcha_chk,$captcha_error);
+ my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ if ($captcha eq 'original') {
+ ($captcha_chk,$captcha_error) = &check_captcha();
+ } elsif ($captcha eq 'recaptcha') {
+ $captcha_chk = &check_recaptcha($privkey);
+ } else {
+ $captcha_chk = 1;
+ }
+ return ($captcha_chk,$captcha_error);
+}
+
+sub get_captcha_config {
+ my ($context,$lonhost) = @_;
+ my ($captcha,$pubkey,$privkey,$hashtocheck);
+ my $hostname = &Apache::lonnet::hostname($lonhost);
+ my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
+ my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+ if ($context eq 'usercreation') {
+ my %domconfig = &Apache::lonnet::get_dom('configuration',[$context],$serverhomedom);
+ if (ref($domconfig{$context}) eq 'HASH') {
+ $hashtocheck = $domconfig{$context}{'cancreate'};
+ if (ref($hashtocheck) eq 'HASH') {
+ if ($hashtocheck->{'captcha'} eq 'recaptcha') {
+ if (ref($hashtocheck->{'recaptchakeys'}) eq 'HASH') {
+ $pubkey = $hashtocheck->{'recaptchakeys'}{'public'};
+ $privkey = $hashtocheck->{'recaptchakeys'}{'private'};
+ }
+ if ($privkey && $pubkey) {
+ $captcha = 'recaptcha';
+ } else {
+ $captcha = 'original';
+ }
+ } elsif ($hashtocheck->{'captcha'} ne 'notused') {
+ $captcha = 'original';
+ }
+ }
+ } else {
+ $captcha = 'captcha';
+ }
+ } elsif ($context eq 'login') {
+ my %domconfhash = &Apache::loncommon::get_domainconf($serverhomedom);
+ if ($domconfhash{$serverhomedom.'.login.captcha'} eq 'recaptcha') {
+ $pubkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_public'};
+ $privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'};
+ if ($privkey && $pubkey) {
+ $captcha = 'recaptcha';
+ } else {
+ $captcha = 'original';
+ }
+ } elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
+ $captcha = 'original';
+ }
+ }
+ return ($captcha,$pubkey,$privkey);
+}
+
+sub create_captcha {
+ my %captcha_params = &captcha_settings();
+ my ($output,$maxtries,$tries) = ('',10,0);
+ while ($tries < $maxtries) {
+ $tries ++;
+ my $captcha = Authen::Captcha->new (
+ output_folder => $captcha_params{'output_dir'},
+ data_folder => $captcha_params{'db_dir'},
+ );
+ my $md5sum = $captcha->generate_code($captcha_params{'numchars'});
+
+ if (-e $Apache::lonnet::perlvar{'lonCaptchaDir'}.'/'.$md5sum.'.png') {
+ $output = ' '."\n".
+ &mt('Type in the letters/numbers shown below').' '.
+ ' '.
+ ' '.
+ ' ';
+ last;
+ }
+ }
+ return $output;
+}
+
+sub captcha_settings {
+ my %captcha_params = (
+ output_dir => $Apache::lonnet::perlvar{'lonCaptchaDir'},
+ www_output_dir => "/captchaspool",
+ db_dir => $Apache::lonnet::perlvar{'lonCaptchaDb'},
+ numchars => '5',
+ );
+ return %captcha_params;
+}
+
+sub check_captcha {
+ my ($captcha_chk,$captcha_error);
+ my $code = $env{'form.code'};
+ my $md5sum = $env{'form.crypt'};
+ my %captcha_params = &captcha_settings();
+ my $captcha = Authen::Captcha->new(
+ output_folder => $captcha_params{'output_dir'},
+ data_folder => $captcha_params{'db_dir'},
+ );
+ $captcha_chk = $captcha->check_code($code,$md5sum);
+ my %captcha_hash = (
+ 0 => 'Code not checked (file error)',
+ -1 => 'Failed: code expired',
+ -2 => 'Failed: invalid code (not in database)',
+ -3 => 'Failed: invalid code (code does not match crypt)',
+ );
+ if ($captcha_chk != 1) {
+ $captcha_error = $captcha_hash{$captcha_chk}
+ }
+ return ($captcha_chk,$captcha_error);
+}
+
+sub create_recaptcha {
+ my ($pubkey) = @_;
+ my $use_ssl;
+ if ($ENV{'SERVER_PORT'} == 443) {
+ $use_ssl = 1;
+ }
+ my $captcha = Captcha::reCAPTCHA->new;
+ return $captcha->get_options_setter({theme => 'white'})."\n".
+ $captcha->get_html($pubkey,undef,$use_ssl).
+ &mt('If either word is hard to read, [_1] will replace them.',
+ ' ').
+ ' ';
+}
+
+sub check_recaptcha {
+ my ($privkey) = @_;
+ my $captcha_chk;
+ my $captcha = Captcha::reCAPTCHA->new;
+ my $captcha_result =
+ $captcha->check_answer(
+ $privkey,
+ $ENV{'REMOTE_ADDR'},
+ $env{'form.recaptcha_challenge_field'},
+ $env{'form.recaptcha_response_field'},
+ );
+ if ($captcha_result->{is_valid}) {
+ $captcha_chk = 1;
+ }
+ return $captcha_chk;
+}
+
+sub emailusername_info {
+ my @fields = ('firstname','lastname','institution','web','location','officialemail');
+ my %titles = &Apache::lonlocal::texthash (
+ lastname => 'Last Name',
+ firstname => 'First Name',
+ institution => 'School/college/university',
+ location => "School's city, state/province, country",
+ web => "School's web address",
+ officialemail => 'E-mail address at institution (if different)',
+ );
+ return (\@fields,\%titles);
+}
+
+sub cleanup_html {
+ my ($incoming) = @_;
+ my $outgoing;
+ if ($incoming ne '') {
+ $outgoing = $incoming;
+ $outgoing =~ s/;/;/g;
+ $outgoing =~ s/\#/#/g;
+ $outgoing =~ s/\&/&/g;
+ $outgoing =~ s/</g;
+ $outgoing =~ s/>/>/g;
+ $outgoing =~ s/\(/(/g;
+ $outgoing =~ s/\)/)/g;
+ $outgoing =~ s/"/"/g;
+ $outgoing =~ s/'/'/g;
+ $outgoing =~ s/\$/$/g;
+ $outgoing =~ s{/}{/}g;
+ $outgoing =~ s/=/=/g;
+ $outgoing =~ s/\\/\/g
+ }
+ return $outgoing;
+}
+
+# Checks for critical messages and returns a redirect url if one exists.
+# $interval indicates how often to check for messages.
+sub critical_redirect {
+ my ($interval) = @_;
+ if ((time-$env{'user.criticalcheck.time'})>$interval) {
+ my @what=&Apache::lonnet::dump('critical', $env{'user.domain'},
+ $env{'user.name'});
+ &Apache::lonnet::appenv({'user.criticalcheck.time'=>time});
+ my $redirecturl;
+ if ($what[0]) {
+ if (($what[0] ne 'con_lost') && ($what[0]!~/^error\:/)) {
+ $redirecturl='/adm/email?critical=display';
+ my $url=&Apache::lonnet::absolute_url().$redirecturl;
+ return (1, $url);
+ }
+ }
+ }
+ return ();
+}
+
+# Use:
+# my $answer=reply("encrypt:passwd:$udom:$uname:$upass",$tryserver);
+#
+##################################################
+# password associated functions #
+##################################################
+sub des_keys {
+ # Make a new key for DES encryption.
+ # Each key has two parts which are returned separately.
+ # Please note: Each key must be passed through the &hex function
+ # before it is output to the web browser. The hex versions cannot
+ # be used to decrypt.
+ my @hexstr=('0','1','2','3','4','5','6','7',
+ '8','9','a','b','c','d','e','f');
+ my $lkey='';
+ for (0..7) {
+ $lkey.=$hexstr[rand(15)];
+ }
+ my $ukey='';
+ for (0..7) {
+ $ukey.=$hexstr[rand(15)];
+ }
+ return ($lkey,$ukey);
+}
+
+sub des_decrypt {
+ my ($key,$cyphertext) = @_;
+ my $keybin=pack("H16",$key);
+ my $cypher;
+ if ($Crypt::DES::VERSION>=2.03) {
+ $cypher=new Crypt::DES $keybin;
+ } else {
+ $cypher=new DES $keybin;
+ }
+ my $plaintext=
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16))));
+ $plaintext.=
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16))));
+ $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) );
+ return $plaintext;
+}
+
1;
__END__;