//
ENDUPDATE
@@ -8091,14 +10079,21 @@ function expand_div(caller) {
sub simple_error_page {
my ($r,$title,$msg,$args) = @_;
+ my %displayargs;
if (ref($args) eq 'HASH') {
if (!$args->{'no_auto_mt_msg'}) { $msg = &mt($msg); }
+ if ($args->{'only_body'}) {
+ $displayargs{'only_body'} = 1;
+ }
+ if ($args->{'no_nav_bar'}) {
+ $displayargs{'no_nav_bar'} = 1;
+ }
} else {
$msg = &mt($msg);
}
my $page =
- &Apache::loncommon::start_page($title).
+ &Apache::loncommon::start_page($title,'',\%displayargs).
''.$msg.'
'.
&Apache::loncommon::end_page();
if (ref($r)) {
@@ -8315,7 +10310,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;
@@ -9023,8 +11018,24 @@ sub get_secgrprole_info {
}
sub user_picker {
- my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context) = @_;
+ my ($dom,$srch,$forcenewuser,$caller,$cancreate,$usertype,$context,$fixeddom,$noinstd) = @_;
my $currdom = $dom;
+ my @alldoms = &Apache::lonnet::all_domains();
+ if (@alldoms == 1) {
+ my %domsrch = &Apache::lonnet::get_dom('configuration',
+ ['directorysrch'],$alldoms[0]);
+ my $domdesc = &Apache::lonnet::domain($alldoms[0],'description');
+ my $showdom = $domdesc;
+ if ($showdom eq '') {
+ $showdom = $dom;
+ }
+ if (ref($domsrch{'directorysrch'}) eq 'HASH') {
+ if ((!$domsrch{'directorysrch'}{'available'}) &&
+ ($domsrch{'directorysrch'}{'lcavailable'} eq '0')) {
+ return (&mt('LON-CAPA directory search is not available in domain: [_1]',$showdom),0);
+ }
+ }
+ }
my %curr_selected = (
srchin => 'dom',
srchby => 'lastname',
@@ -9045,7 +11056,7 @@ sub user_picker {
}
$srchterm = $srch->{'srchterm'};
}
- my %lt=&Apache::lonlocal::texthash(
+ my %html_lt=&Apache::lonlocal::texthash(
'usr' => 'Search criteria',
'doma' => 'Domain/institution to search',
'uname' => 'username',
@@ -9058,6 +11069,8 @@ sub user_picker {
'exact' => 'is',
'contains' => 'contains',
'begins' => 'begins with',
+ );
+ my %js_lt=&Apache::lonlocal::texthash(
'youm' => "You must include some text to search for.",
'thte' => "The text you are searching for must contain at least two characters when using a 'begins' type search.",
'thet' => "The text you are searching for must contain at least three characters when using a 'contains' type search.",
@@ -9067,7 +11080,16 @@ sub user_picker {
'whse' => "When searching by last,first you must include at least one character in the first name.",
'thfo' => "The following need to be corrected before the search can be run:",
);
- my $domform = &select_dom_form($currdom,'srchdomain',1,1);
+ &html_escape(\%html_lt);
+ &js_escape(\%js_lt);
+ my $domform;
+ my $allow_blank = 1;
+ if ($fixeddom) {
+ $allow_blank = 0;
+ $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1,undef,[$currdom]);
+ } else {
+ $domform = &select_dom_form($currdom,'srchdomain',$allow_blank,1);
+ }
my $srchinsel = ' ';
my @srchins = ('crs','dom','alc','instd');
@@ -9079,12 +11101,13 @@ sub user_picker {
next if ($option eq 'alc');
next if (($option eq 'crs') && ($env{'form.form'} eq 'requestcrs'));
next if ($option eq 'crs' && !$env{'request.course.id'});
+ next if (($option eq 'instd') && ($noinstd));
if ($curr_selected{'srchin'} eq $option) {
$srchinsel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
} else {
$srchinsel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
}
}
$srchinsel .= "\n \n";
@@ -9093,10 +11116,10 @@ sub user_picker {
foreach my $option ('lastname','lastfirst','uname') {
if ($curr_selected{'srchby'} eq $option) {
$srchbysel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
} else {
$srchbysel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
}
}
$srchbysel .= "\n \n";
@@ -9105,10 +11128,10 @@ sub user_picker {
foreach my $option ('begins','contains','exact') {
if ($curr_selected{'srchtype'} eq $option) {
$srchtypesel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
} else {
$srchtypesel .= '
- '.$lt{$option}.' ';
+ '.$html_lt{$option}.' ';
}
}
$srchtypesel .= "\n \n";
@@ -9193,46 +11216,46 @@ function validateEntry(callingForm) {
if (srchterm == "") {
checkok = 0;
- msg += "$lt{'youm'}\\n";
+ msg += "$js_lt{'youm'}\\n";
}
if (srchtype== 'begins') {
if (srchterm.length < 2) {
checkok = 0;
- msg += "$lt{'thte'}\\n";
+ msg += "$js_lt{'thte'}\\n";
}
}
if (srchtype== 'contains') {
if (srchterm.length < 3) {
checkok = 0;
- msg += "$lt{'thet'}\\n";
+ msg += "$js_lt{'thet'}\\n";
}
}
if (srchin == 'instd') {
if (srchdomain == '') {
checkok = 0;
- msg += "$lt{'yomc'}\\n";
+ msg += "$js_lt{'yomc'}\\n";
}
}
if (srchin == 'dom') {
if (srchdomain == '') {
checkok = 0;
- msg += "$lt{'ymcd'}\\n";
+ msg += "$js_lt{'ymcd'}\\n";
}
}
if (srchby == 'lastfirst') {
if (srchterm.indexOf(",") == -1) {
checkok = 0;
- msg += "$lt{'whus'}\\n";
+ msg += "$js_lt{'whus'}\\n";
}
if (srchterm.indexOf(",") == srchterm.length -1) {
checkok = 0;
- msg += "$lt{'whse'}\\n";
+ msg += "$js_lt{'whse'}\\n";
}
}
if (checkok == 0) {
- alert("$lt{'thfo'}\\n"+msg);
+ alert("$js_lt{'thfo'}\\n"+msg);
return;
}
if (checkok == 1) {
@@ -9250,10 +11273,10 @@ $new_user_create
END_BLOCK
$output .= &Apache::lonhtmlcommon::start_pick_box().
- &Apache::lonhtmlcommon::row_title($lt{'doma'}).
+ &Apache::lonhtmlcommon::row_title($html_lt{'doma'}).
$domform.
&Apache::lonhtmlcommon::row_closure().
- &Apache::lonhtmlcommon::row_title($lt{'usr'}).
+ &Apache::lonhtmlcommon::row_title($html_lt{'usr'}).
$srchbysel.
$srchtypesel.
' '.
@@ -9261,61 +11284,165 @@ END_BLOCK
&Apache::lonhtmlcommon::row_closure(1).
&Apache::lonhtmlcommon::end_pick_box().
' ';
- return $output;
+ return ($output,1);
}
sub user_rule_check {
my ($usershash,$checks,$alerts,$rulematch,$inst_results,$curr_rules,$got_rules) = @_;
- my $response;
+ my ($response,%inst_response);
if (ref($usershash) eq 'HASH') {
- foreach my $user (keys(%{$usershash})) {
- my ($uname,$udom) = split(/:/,$user);
- next if ($udom eq '' || $uname eq '');
- my ($id,$newuser);
- if (ref($usershash->{$user}) eq 'HASH') {
- $newuser = $usershash->{$user}->{'newuser'};
- $id = $usershash->{$user}->{'id'};
- }
- my $inst_response;
+ if (keys(%{$usershash}) > 1) {
+ my (%by_username,%by_id,%userdoms);
+ my $checkid;
if (ref($checks) eq 'HASH') {
- if (defined($checks->{'username'})) {
- ($inst_response,%{$inst_results->{$user}}) =
- &Apache::lonnet::get_instuser($udom,$uname);
- } elsif (defined($checks->{'id'})) {
- ($inst_response,%{$inst_results->{$user}}) =
- &Apache::lonnet::get_instuser($udom,undef,$id);
+ if ((!defined($checks->{'username'})) && (defined($checks->{'id'}))) {
+ $checkid = 1;
+ }
+ }
+ foreach my $user (keys(%{$usershash})) {
+ my ($uname,$udom) = split(/:/,$user);
+ if ($checkid) {
+ if (ref($usershash->{$user}) eq 'HASH') {
+ if ($usershash->{$user}->{'id'} ne '') {
+ $by_id{$udom}{$usershash->{$user}->{'id'}} = $uname;
+ $userdoms{$udom} = 1;
+ if (ref($inst_results) eq 'HASH') {
+ $inst_results->{$uname.':'.$udom} = {};
+ }
+ }
+ }
+ } else {
+ $by_username{$udom}{$uname} = 1;
+ $userdoms{$udom} = 1;
+ if (ref($inst_results) eq 'HASH') {
+ $inst_results->{$uname.':'.$udom} = {};
+ }
+ }
+ }
+ foreach my $udom (keys(%userdoms)) {
+ if (!$got_rules->{$udom}) {
+ my %domconfig = &Apache::lonnet::get_dom('configuration',
+ ['usercreation'],$udom);
+ if (ref($domconfig{'usercreation'}) eq 'HASH') {
+ foreach my $item ('username','id') {
+ if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+ $$curr_rules{$udom}{$item} =
+ $domconfig{'usercreation'}{$item.'_rule'};
+ }
+ }
+ }
+ $got_rules->{$udom} = 1;
+ }
+ }
+ if ($checkid) {
+ foreach my $udom (keys(%by_id)) {
+ my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_id{$udom},'id');
+ if ($outcome eq 'ok') {
+ foreach my $id (keys(%{$by_id{$udom}})) {
+ my $uname = $by_id{$udom}{$id};
+ $inst_response{$uname.':'.$udom} = $outcome;
+ }
+ if (ref($results) eq 'HASH') {
+ foreach my $uname (keys(%{$results})) {
+ if (exists($inst_response{$uname.':'.$udom})) {
+ $inst_response{$uname.':'.$udom} = $outcome;
+ $inst_results->{$uname.':'.$udom} = $results->{$uname};
+ }
+ }
+ }
+ }
}
} else {
- ($inst_response,%{$inst_results->{$user}}) =
- &Apache::lonnet::get_instuser($udom,$uname);
- return;
+ foreach my $udom (keys(%by_username)) {
+ my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_username{$udom});
+ if ($outcome eq 'ok') {
+ foreach my $uname (keys(%{$by_username{$udom}})) {
+ $inst_response{$uname.':'.$udom} = $outcome;
+ }
+ if (ref($results) eq 'HASH') {
+ foreach my $uname (keys(%{$results})) {
+ $inst_results->{$uname.':'.$udom} = $results->{$uname};
+ }
+ }
+ }
+ }
}
- if (!$got_rules->{$udom}) {
- my %domconfig = &Apache::lonnet::get_dom('configuration',
- ['usercreation'],$udom);
- if (ref($domconfig{'usercreation'}) eq 'HASH') {
- foreach my $item ('username','id') {
- if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
- $$curr_rules{$udom}{$item} =
- $domconfig{'usercreation'}{$item.'_rule'};
+ } elsif (keys(%{$usershash}) == 1) {
+ my $user = (keys(%{$usershash}))[0];
+ my ($uname,$udom) = split(/:/,$user);
+ if (($udom ne '') && ($uname ne '')) {
+ if (ref($usershash->{$user}) eq 'HASH') {
+ if (ref($checks) eq 'HASH') {
+ if (defined($checks->{'username'})) {
+ ($inst_response{$user},%{$inst_results->{$user}}) =
+ &Apache::lonnet::get_instuser($udom,$uname);
+ } elsif (defined($checks->{'id'})) {
+ if ($usershash->{$user}->{'id'} ne '') {
+ ($inst_response{$user},%{$inst_results->{$user}}) =
+ &Apache::lonnet::get_instuser($udom,undef,
+ $usershash->{$user}->{'id'});
+ } else {
+ ($inst_response{$user},%{$inst_results->{$user}}) =
+ &Apache::lonnet::get_instuser($udom,$uname);
+ }
}
+ } else {
+ ($inst_response{$user},%{$inst_results->{$user}}) =
+ &Apache::lonnet::get_instuser($udom,$uname);
+ return;
+ }
+ if (!$got_rules->{$udom}) {
+ my %domconfig = &Apache::lonnet::get_dom('configuration',
+ ['usercreation'],$udom);
+ if (ref($domconfig{'usercreation'}) eq 'HASH') {
+ foreach my $item ('username','id') {
+ if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') {
+ $$curr_rules{$udom}{$item} =
+ $domconfig{'usercreation'}{$item.'_rule'};
+ }
+ }
+ }
+ $got_rules->{$udom} = 1;
}
}
- $got_rules->{$udom} = 1;
+ } else {
+ return;
+ }
+ } else {
+ return;
+ }
+ foreach my $user (keys(%{$usershash})) {
+ my ($uname,$udom) = split(/:/,$user);
+ next if (($udom eq '') || ($uname eq ''));
+ my $id;
+ if (ref($inst_results) eq 'HASH') {
+ if (ref($inst_results->{$user}) eq 'HASH') {
+ $id = $inst_results->{$user}->{'id'};
+ }
+ }
+ if ($id eq '') {
+ if (ref($usershash->{$user})) {
+ $id = $usershash->{$user}->{'id'};
+ }
}
foreach my $item (keys(%{$checks})) {
if (ref($$curr_rules{$udom}) eq 'HASH') {
if (ref($$curr_rules{$udom}{$item}) eq 'ARRAY') {
if (@{$$curr_rules{$udom}{$item}} > 0) {
- my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,$$curr_rules{$udom}{$item});
+ my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,
+ $$curr_rules{$udom}{$item});
foreach my $rule (@{$$curr_rules{$udom}{$item}}) {
if ($rule_check{$rule}) {
$$rulematch{$user}{$item} = $rule;
- if ($inst_response eq 'ok') {
+ if ($inst_response{$user} eq 'ok') {
if (ref($inst_results) eq 'HASH') {
if (ref($inst_results->{$user}) eq 'HASH') {
if (keys(%{$inst_results->{$user}}) == 0) {
$$alerts{$item}{$udom}{$uname} = 1;
+ } elsif ($item eq 'id') {
+ if ($inst_results->{$user}->{'id'} eq '') {
+ $$alerts{$item}{$udom}{$uname} = 1;
+ }
}
}
}
@@ -9454,11 +11581,15 @@ sub sorted_inst_types {
}
sub get_institutional_codes {
- my ($settings,$allcourses,$LC_code) = @_;
+ my ($cdom,$crs,$settings,$allcourses,$LC_code) = @_;
# Get complete list of course sections to update
my @currsections = ();
my @currxlists = ();
+ my (%unclutteredsec,%unclutteredlcsec);
my $coursecode = $$settings{'internal.coursecode'};
+ my $crskey = $crs.':'.$coursecode;
+ @{$unclutteredsec{$crskey}} = ();
+ @{$unclutteredlcsec{$crskey}} = ();
if ($$settings{'internal.sectionnums'} ne '') {
@currsections = split(/,/,$$settings{'internal.sectionnums'});
@@ -9469,24 +11600,37 @@ sub get_institutional_codes {
}
if (@currxlists > 0) {
- foreach (@currxlists) {
- if (m/^([^:]+):(\w*)$/) {
+ foreach my $xl (@currxlists) {
+ if ($xl =~ /^([^:]+):(\w*)$/) {
unless (grep/^$1$/,@{$allcourses}) {
- push @{$allcourses},$1;
+ push(@{$allcourses},$1);
$$LC_code{$1} = $2;
}
}
}
}
-
+
if (@currsections > 0) {
- foreach (@currsections) {
- if (m/^(\w+):(\w*)$/) {
- my $sec = $coursecode.$1;
+ foreach my $sec (@currsections) {
+ if ($sec =~ m/^(\w+):(\w*)$/ ) {
+ my $instsec = $1;
my $lc_sec = $2;
- unless (grep/^$sec$/,@{$allcourses}) {
- push @{$allcourses},$sec;
- $$LC_code{$sec} = $lc_sec;
+ unless (grep/^\Q$instsec\E$/,@{$unclutteredsec{$crskey}}) {
+ push(@{$unclutteredsec{$crskey}},$instsec);
+ push(@{$unclutteredlcsec{$crskey}},$lc_sec);
+ }
+ }
+ }
+ }
+
+ if (@{$unclutteredsec{$crskey}} > 0) {
+ my %formattedsec = &Apache::lonnet::auto_instsec_reformat($cdom,'clutter',\%unclutteredsec);
+ if ((ref($formattedsec{$crskey}) eq 'ARRAY') && (ref($unclutteredlcsec{$crskey}) eq 'ARRAY')) {
+ for (my $i=0; $i<@{$formattedsec{$crskey}}; $i++) {
+ my $sec = $coursecode.$formattedsec{$crskey}[$i];
+ unless (grep/^\Q$sec\E$/,@{$allcourses}) {
+ push(@{$allcourses},$sec);
+ $$LC_code{$sec} = $unclutteredlcsec{$crskey}[$i];
}
}
}
@@ -9583,7 +11727,9 @@ reservable_now - ref to hash of student_
Keys in inner hash are:
(a) symb: either blank or symb to which slot use is restricted.
- (b) endreserve: end date of reservation period.
+ (b) endreserve: end date of reservation period.
+ (c) uniqueperiod: start,end dates when slot is to be uniquely
+ selected.
sorted_future - ref to array of student_schedulable slots reservable in
the future, ordered by start date of reservation period.
@@ -9594,6 +11740,8 @@ future_reservable - ref to hash of stude
Keys in inner hash are:
(a) symb: either blank or symb to which slot use is restricted.
(b) startreserve: start date of reservation period.
+ (c) uniqueperiod: start,end dates when slot is to be uniquely
+ selected.
=back
@@ -9647,6 +11795,10 @@ sub get_future_slots {
my $startreserve = $slots{$slot}->{'startreserve'};
my $endreserve = $slots{$slot}->{'endreserve'};
my $symb = $slots{$slot}->{'symb'};
+ my $uniqueperiod;
+ if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') {
+ $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}});
+ }
if (($startreserve < $now) &&
(!$endreserve || $endreserve > $now)) {
my $lastres = $endreserve;
@@ -9655,13 +11807,15 @@ sub get_future_slots {
}
$reservable_now{$slot} = {
symb => $symb,
- endreserve => $lastres
+ endreserve => $lastres,
+ uniqueperiod => $uniqueperiod,
};
} elsif (($startreserve > $now) &&
(!$endreserve || $endreserve > $startreserve)) {
$future_reservable{$slot} = {
symb => $symb,
- startreserve => $startreserve
+ startreserve => $startreserve,
+ uniqueperiod => $uniqueperiod,
};
}
}
@@ -9896,7 +12050,15 @@ sub ask_for_embedded_content {
($path) =
($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
}
- $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+ if ($toplevel=~/^\/*(uploaded|editupload)/) {
+ $fileloc = $toplevel;
+ $fileloc=~ s/^\s*(\S+)\s*$/$1/;
+ my ($udom,$uname,$fname) =
+ ($fileloc=~ m{^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$});
+ $fileloc = propath($udom,$uname).'/userfiles/'.$fname;
+ } else {
+ $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+ }
$fileloc =~ s{^/}{};
($filename) = ($fileloc =~ m{.+/([^/]+)$});
$heading = &mt('Status of dependencies in [_1]',"$title ($filename)");
@@ -10718,7 +12880,7 @@ sub modify_html_refs {
return;
}
}
- if (open(my $fh,"<$container")) {
+ if (open(my $fh,'<',$container)) {
$content = join('', <$fh>);
close($fh);
} else {
@@ -10783,7 +12945,7 @@ sub modify_html_refs {
}
}
} else {
- if (open(my $fh,">$container")) {
+ if (open(my $fh,'>',$container)) {
print $fh $content;
close($fh);
$output = ''.&mt('Updated [quant,_1,reference] in [_2].',
@@ -11052,7 +13214,7 @@ sub decompress_form {
"$topdir/media/player.swf",
"$topdir/media/swfobject.js",
"$topdir/media/expressInstall.swf");
- my @camtasia8 = ("$topdir/","$topdir/$topdir.html",
+ my @camtasia8_1 = ("$topdir/","$topdir/$topdir.html",
"$topdir/$topdir.mp4",
"$topdir/$topdir\_config.xml",
"$topdir/$topdir\_controller.swf",
@@ -11074,13 +13236,36 @@ sub decompress_form {
"$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);
+ @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;
+ }
}
}
}
@@ -11277,6 +13462,18 @@ sub decompress_uploaded_file {
sub process_decompression {
my ($docudom,$docuname,$file,$destination,$dir_root,$hiddenelem) = @_;
+ unless (($dir_root eq '/userfiles') && ($destination =~ m{^(docs|supplemental)/(default|\d+)/\d+$})) {
+ return '
'.&mt('Not extracted.').' '.
+ &mt('Unexpected file path.').'
'."\n";
+ }
+ unless (($docudom =~ /^$match_domain$/) && ($docuname =~ /^$match_courseid$/)) {
+ return ''.&mt('Not extracted.').' '.
+ &mt('Unexpected course context.').'
'."\n";
+ }
+ unless ($file eq &Apache::lonnet::clean_filename($file)) {
+ return ''.&mt('Not extracted.').' '.
+ &mt('Filename contained unexpected characters.').'
'."\n";
+ }
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.').
@@ -11311,30 +13508,44 @@ sub process_decompression {
}
}
my $numskip = scalar(@to_skip);
- if (($numskip > 0) &&
- ($numskip == $env{'form.archive_itemcount'})) {
+ my $numoverwrite = scalar(@to_overwrite);
+ if (($numskip) && (!$numoverwrite)) {
$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) {
+ if (($numskip) || ($numoverwrite)) {
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");
+ if (&File::Copy::move("$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") {
+ &File::Path::remove_tree("$dir/$tempdir/$item",{ safe => 1 });
+ }
}
}
+ foreach my $item (@to_overwrite) {
+ if ((-e "$dir/$tempdir/$item") && (-e "$dir/$item")) {
+ if (($item ne '') && ($item !~ /\.\./)) {
+ if (-f "$dir/$item") {
+ unlink("$dir/$item");
+ } elsif (-d "$dir/$item") {
+ &File::Path::remove_tree("$dir/$item",{ safe => 1 });
+ }
+ &File::Copy::move("$dir/$tempdir/$item","$dir/$item");
+ }
+ }
+ }
+ if (&File::Copy::move("$dir/$tempdir/$file","$dir/$file")) {
+ &File::Path::remove_tree("$dir/$tempdir",{ safe => 1 });
+ }
}
- system("mv $dir/$tempdir/* $dir");
- rmdir("$dir/$tempdir");
} else {
($decompressed,$display) =
&decompress_uploaded_file($file,$dir);
@@ -11352,8 +13563,7 @@ sub process_decompression {
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)))) {
+ unless (($item =~ /^\.+$/) || ($item eq $file)) {
push(@newitems,$item);
if ($dirptr&$testdir) {
$is_dir{$item} = 1;
@@ -11838,7 +14048,7 @@ END
sub process_extracted_files {
my ($context,$docudom,$docuname,$destination,$dir_root,$hiddenelem) = @_;
my $numitems = $env{'form.archive_count'};
- return unless ($numitems);
+ return if ((!$numitems) || ($numitems =~ /\D/));
my @ids=&Apache::lonnet::current_machine_ids();
my ($prefix,$pathtocheck,$dir,$ishome,$error,$warning,%toplevelitems,%is_dir,
%folders,%containers,%mapinner,%prompttofetch);
@@ -11851,7 +14061,7 @@ sub process_extracted_files {
} else {
$prefix = $Apache::lonnet::perlvar{'lonDocRoot'};
$pathtocheck = "$dir_root/$docudom/$docuname/$destination";
- $dir = "$dir_root/$docudom/$docuname";
+ $dir = "$dir_root/$docudom/$docuname";
}
my $currdir = "$dir_root/$destination";
(my $docstype,$mapinner{'0'}) = ($destination =~ m{^(docs|supplemental)/(\w+)/});
@@ -11940,7 +14150,9 @@ sub process_extracted_files {
'.'.$containers{$outer},1,1);
$newseqid{$i} = $newidx;
unless ($errtext) {
- $result .= ''.&mt('Folder: [_1] added to course',$docstitle).' '."\n";
+ $result .= ''.&mt('Folder: [_1] added to course',
+ &HTML::Entities::encode($docstitle,'<>&"'))..
+ ' '."\n";
}
}
} else {
@@ -11949,38 +14161,49 @@ sub process_extracted_files {
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;
+ if (($outer !~ /\D/) &&
+ (($mapinner{$outer} eq 'default') || ($mapinner{$outer} !~ /\D/)) &&
+ ($newidx !~ /\D/)) {
+ if (!-e "$prefix$dir/$docstype/$mapinner{$outer}") {
+ mkdir("$prefix$dir/$docstype/$mapinner{$outer}",0755);
}
- }
- $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";
+ if (!-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+ mkdir("$prefix$dir/$docstype/$mapinner{$outer}/$newidx");
}
+ if (-e "$prefix$dir/$docstype/$mapinner{$outer}/$newidx") {
+ if (rename("$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',
+ &HTML::Entities::encode($docstitle,'<>&"')).
+ ' '."\n";
+ }
+ }
+ } else {
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+ &HTML::Entities::encode($path,'<>&"')).' ';
}
}
}
}
} else {
- $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).' ';
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+ &HTML::Entities::encode($path,'<>&"')).' ';
}
}
for (my $i=1; $i<=$numitems; $i++) {
@@ -12041,7 +14264,9 @@ sub process_extracted_files {
}
if ($fullpath ne '') {
if (-e "$prefix$path") {
- system("mv $prefix$path $fullpath/$title");
+ unless (rename("$prefix$path","$fullpath/$title")) {
+ $warning .= &mt('Failed to rename dependency').' ';
+ }
}
if (-e "$fullpath/$title") {
my $showpath;
@@ -12050,21 +14275,26 @@ sub process_extracted_files {
} 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;
+ $result .= ''.&mt('[_1] included as a dependency',
+ &HTML::Entities::encode($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}}).' ';
+ &HTML::Entities::encode($path,'<>&"'),
+ &HTML::Entities::encode($env{'form.archive_content_'.$referrer{$i}},'<>&"')).
+ ' ';
}
} else {
- $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).' ';
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',
+ &HTML::Entities::encode($path)).' ';
}
}
if (keys(%todelete)) {
@@ -12338,12 +14568,15 @@ sub upfile_store {
$env{'form.upfile'}=~s/\n+/\n/gs;
$env{'form.upfile'}=~s/\n+$//gs;
- my $datatoken=$env{'user.name'}.'_'.$env{'user.domain'}.
- '_enroll_'.$env{'request.course.id'}.'_'.time.'_'.$$;
+ my $datatoken = &valid_datatoken($env{'user.name'}.'_'.$env{'user.domain'}.
+ '_enroll_'.$env{'request.course.id'}.'_'.
+ time.'_'.$$);
+ return if ($datatoken eq '');
+
{
my $datafile = $r->dir_config('lonDaemons').
'/tmp/'.$datatoken.'.tmp';
- if ( open(my $fh,">$datafile") ) {
+ if ( open(my $fh,'>',$datafile) ) {
print $fh $env{'form.upfile'};
close($fh);
}
@@ -12353,21 +14586,22 @@ sub upfile_store {
=pod
-=item * &load_tmp_file($r)
+=item * &load_tmp_file($r,$datatoken)
Load uploaded file from tmp, $r should be the HTTP Request object,
-needs $env{'form.datatoken'},
+$datatoken is the name to assign to the temporary file.
sets $env{'form.upfile'} to the contents of the file
=cut
sub load_tmp_file {
- my $r=shift;
+ my ($r,$datatoken) = @_;
+ return if ($datatoken eq '');
my @studentdata=();
{
my $studentfile = $r->dir_config('lonDaemons').
- '/tmp/'.$env{'form.datatoken'}.'.tmp';
- if ( open(my $fh,"<$studentfile") ) {
+ '/tmp/'.$datatoken.'.tmp';
+ if ( open(my $fh,'<',$studentfile) ) {
@studentdata=<$fh>;
close($fh);
}
@@ -12375,6 +14609,14 @@ sub load_tmp_file {
$env{'form.upfile'}=join('',@studentdata);
}
+sub valid_datatoken {
+ my ($datatoken) = @_;
+ if ($datatoken =~ /^$match_username\_$match_domain\_enroll_(|$match_domain\_$match_courseid)\_\d+_\d+$/) {
+ return $datatoken;
+ }
+ return;
+}
+
=pod
=item * &upfile_record_sep()
@@ -12815,7 +15057,7 @@ sub DrawBarGraph {
@Labels = @$labels;
} else {
for (my $i=0;$i<@{$Values[0]};$i++) {
- push (@Labels,$i+1);
+ push(@Labels,$i+1);
}
}
#
@@ -13263,6 +15505,12 @@ defdom (domain for which to retrieve con
origmail (scalar - email address of recipient from loncapa.conf,
i.e., predates configuration by DC via domainprefs.pm
+$requname username of requester (if mailing type is helpdeskmail)
+
+$requdom domain of requester (if mailing type is helpdeskmail)
+
+$reqemail e-mail address of requester (if mailing type is helpdeskmail)
+
Returns: comma separated list of addresses to which to send e-mail.
=back
@@ -13272,11 +15520,11 @@ Returns: comma separated list of address
############################################################
############################################################
sub build_recipient_list {
- my ($defmail,$mailing,$defdom,$origmail) = @_;
+ my ($defmail,$mailing,$defdom,$origmail,$requname,$requdom,$reqemail) = @_;
my @recipients;
- my $otheremails;
+ my ($otheremails,$lastresort,$allbcc,$addtext);
my %domconfig =
- &Apache::lonnet::get_dom('configuration',['contacts'],$defdom);
+ &Apache::lonnet::get_dom('configuration',['contacts'],$defdom);
if (ref($domconfig{'contacts'}) eq 'HASH') {
if (exists($domconfig{'contacts'}{$mailing})) {
if (ref($domconfig{'contacts'}{$mailing}) eq 'HASH') {
@@ -13288,14 +15536,183 @@ sub build_recipient_list {
push(@recipients,$addr);
}
}
- $otheremails = $domconfig{'contacts'}{$mailing}{'others'};
+ }
+ $otheremails = $domconfig{'contacts'}{$mailing}{'others'};
+ if ($mailing eq 'helpdeskmail') {
+ if ($domconfig{'contacts'}{$mailing}{'bcc'}) {
+ my @bccs = split(/,/,$domconfig{'contacts'}{$mailing}{'bcc'});
+ my @ok_bccs;
+ foreach my $bcc (@bccs) {
+ $bcc =~ s/^\s+//g;
+ $bcc =~ s/\s+$//g;
+ if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+ if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+ push(@ok_bccs,$bcc);
+ }
+ }
+ }
+ if (@ok_bccs > 0) {
+ $allbcc = join(', ',@ok_bccs);
+ }
+ }
+ $addtext = $domconfig{'contacts'}{$mailing}{'include'};
}
}
} elsif ($origmail ne '') {
- push(@recipients,$origmail);
+ $lastresort = $origmail;
+ }
+ if ($mailing eq 'helpdeskmail') {
+ if ((ref($domconfig{'contacts'}{'overrides'}) eq 'HASH') &&
+ (keys(%{$domconfig{'contacts'}{'overrides'}}))) {
+ my ($inststatus,$inststatus_checked);
+ if (($env{'user.name'} ne '') && ($env{'user.domain'} ne '') &&
+ ($env{'user.domain'} ne 'public')) {
+ $inststatus_checked = 1;
+ $inststatus = $env{'environment.inststatus'};
+ }
+ unless ($inststatus_checked) {
+ if (($requname ne '') && ($requdom ne '')) {
+ if (($requname =~ /^$match_username$/) &&
+ ($requdom =~ /^$match_domain$/) &&
+ (&Apache::lonnet::domain($requdom))) {
+ my $requhome = &Apache::lonnet::homeserver($requname,
+ $requdom);
+ unless ($requhome eq 'no_host') {
+ my %userenv = &Apache::lonnet::userenvironment($requdom,$requname,'inststatus');
+ $inststatus = $userenv{'inststatus'};
+ $inststatus_checked = 1;
+ }
+ }
+ }
+ }
+ unless ($inststatus_checked) {
+ if ($reqemail =~ /^[^\@]+\@[^\@]+$/) {
+ my %srch = (srchby => 'email',
+ srchdomain => $defdom,
+ srchterm => $reqemail,
+ srchtype => 'exact');
+ my %srch_results = &Apache::lonnet::usersearch(\%srch);
+ foreach my $uname (keys(%srch_results)) {
+ if (ref($srch_results{$uname}{'inststatus'}) eq 'ARRAY') {
+ $inststatus = join(',',@{$srch_results{$uname}{'inststatus'}});
+ $inststatus_checked = 1;
+ last;
+ }
+ }
+ unless ($inststatus_checked) {
+ my ($dirsrchres,%srch_results) = &Apache::lonnet::inst_directory_query(\%srch);
+ if ($dirsrchres eq 'ok') {
+ foreach my $uname (keys(%srch_results)) {
+ if (ref($srch_results{$uname}{'inststatus'}) eq 'ARRAY') {
+ $inststatus = join(',',@{$srch_results{$uname}{'inststatus'}});
+ $inststatus_checked = 1;
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ if ($inststatus ne '') {
+ foreach my $status (split(/\:/,$inststatus)) {
+ if (ref($domconfig{'contacts'}{'overrides'}{$status}) eq 'HASH') {
+ my @contacts = ('adminemail','supportemail');
+ foreach my $item (@contacts) {
+ if ($domconfig{'contacts'}{'overrides'}{$status}{$item}) {
+ my $addr = $domconfig{'contacts'}{'overrides'}{$status};
+ if (!grep(/^\Q$addr\E$/,@recipients)) {
+ push(@recipients,$addr);
+ }
+ }
+ }
+ $otheremails = $domconfig{'contacts'}{'overrides'}{$status}{'others'};
+ if ($domconfig{'contacts'}{'overrides'}{$status}{'bcc'}) {
+ my @bccs = split(/,/,$domconfig{'contacts'}{'overrides'}{$status}{'bcc'});
+ my @ok_bccs;
+ foreach my $bcc (@bccs) {
+ $bcc =~ s/^\s+//g;
+ $bcc =~ s/\s+$//g;
+ if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+ if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+ push(@ok_bccs,$bcc);
+ }
+ }
+ }
+ if (@ok_bccs > 0) {
+ $allbcc = join(', ',@ok_bccs);
+ }
+ }
+ $addtext = $domconfig{'contacts'}{'overrides'}{$status}{'include'};
+ last;
+ }
+ }
+ }
+ }
}
} elsif ($origmail ne '') {
- push(@recipients,$origmail);
+ $lastresort = $origmail;
+ }
+ if (($mailing eq 'helpdeskmail') && ($lastresort ne '')) {
+ unless (grep(/^\Q$defdom\E$/,&Apache::lonnet::current_machine_domains())) {
+ my $lonhost = $Apache::lonnet::perlvar{'lonHostID'};
+ my $machinedom = $Apache::lonnet::perlvar{'lonDefDomain'};
+ my %what = (
+ perlvar => 1,
+ );
+ my $primary = &Apache::lonnet::domain($defdom,'primary');
+ if ($primary) {
+ my $gotaddr;
+ my ($result,$returnhash) =
+ &Apache::lonnet::get_remote_globals($primary,{ perlvar => 1 });
+ if (($result eq 'ok') && (ref($returnhash) eq 'HASH')) {
+ if ($returnhash->{'lonSupportEMail'} =~ /^[^\@]+\@[^\@]+$/) {
+ $lastresort = $returnhash->{'lonSupportEMail'};
+ $gotaddr = 1;
+ }
+ }
+ unless ($gotaddr) {
+ my $uintdom = &Apache::lonnet::internet_dom($primary);
+ my $intdom = &Apache::lonnet::internet_dom($lonhost);
+ unless ($uintdom eq $intdom) {
+ my %domconfig =
+ &Apache::lonnet::get_dom('configuration',['contacts'],$machinedom);
+ if (ref($domconfig{'contacts'}) eq 'HASH') {
+ if (ref($domconfig{'contacts'}{'otherdomsmail'}) eq 'HASH') {
+ my @contacts = ('adminemail','supportemail');
+ foreach my $item (@contacts) {
+ if ($domconfig{'contacts'}{'otherdomsmail'}{$item}) {
+ my $addr = $domconfig{'contacts'}{$item};
+ if (!grep(/^\Q$addr\E$/,@recipients)) {
+ push(@recipients,$addr);
+ }
+ }
+ }
+ if ($domconfig{'contacts'}{'otherdomsmail'}{'others'}) {
+ $otheremails = $domconfig{'contacts'}{'otherdomsmail'}{'others'};
+ }
+ if ($domconfig{'contacts'}{'otherdomsmail'}{'bcc'}) {
+ my @bccs = split(/,/,$domconfig{'contacts'}{'otherdomsmail'}{'bcc'});
+ my @ok_bccs;
+ foreach my $bcc (@bccs) {
+ $bcc =~ s/^\s+//g;
+ $bcc =~ s/\s+$//g;
+ if ($bcc =~ m/^[^\@]+\@[^\@]+$/) {
+ if (!(grep(/^\Q$bcc\E$/,@ok_bccs))) {
+ push(@ok_bccs,$bcc);
+ }
+ }
+ }
+ if (@ok_bccs > 0) {
+ $allbcc = join(', ',@ok_bccs);
+ }
+ }
+ $addtext = $domconfig{'contacts'}{'otherdomsmail'}{'include'};
+ }
+ }
+ }
+ }
+ }
+ }
}
if (defined($defmail)) {
if ($defmail ne '') {
@@ -13315,8 +15732,21 @@ sub build_recipient_list {
}
}
}
- my $recipientlist = join(',',@recipients);
- return $recipientlist;
+ if ($mailing eq 'helpdeskmail') {
+ if ((!@recipients) && ($lastresort ne '')) {
+ push(@recipients,$lastresort);
+ }
+ } elsif ($lastresort ne '') {
+ if (!grep(/^\Q$lastresort\E$/,@recipients)) {
+ push(@recipients,$lastresort);
+ }
+ }
+ my $recipientlist = join(',',@recipients);
+ if (wantarray) {
+ return ($recipientlist,$allbcc,$addtext);
+ } else {
+ return $recipientlist;
+ }
}
############################################################
@@ -13407,6 +15837,8 @@ jsarray (reference to array of categorie
subcats (reference to hash of arrays containing all subcategories within each
category, -recursive)
+maxd (reference to hash used to hold max depth for all top-level categories).
+
Returns: nothing
Side effects: populates trails and allitems hash references.
@@ -13414,7 +15846,7 @@ Side effects: populates trails and allit
=cut
sub extract_categories {
- my ($categories,$cats,$trails,$allitems,$idx,$jsarray,$subcats) = @_;
+ my ($categories,$cats,$trails,$allitems,$idx,$jsarray,$subcats,$maxd) = @_;
if (ref($categories) eq 'HASH') {
&gather_categories($categories,$cats,$idx,$jsarray);
if (ref($cats->[0]) eq 'ARRAY') {
@@ -13440,12 +15872,15 @@ sub extract_categories {
if (ref($subcats) eq 'HASH') {
push(@{$subcats->{$item}},&escape($category).':'.&escape($name).':1');
}
- &recurse_categories($cats,2,$category,$trails,$allitems,\@parents,$subcats);
+ &recurse_categories($cats,2,$category,$trails,$allitems,\@parents,$subcats,$maxd);
}
} else {
if (ref($subcats) eq 'HASH') {
$subcats->{$item} = [];
}
+ if (ref($maxd) eq 'HASH') {
+ $maxd->{$name} = 1;
+ }
}
}
}
@@ -13483,13 +15918,13 @@ Side effects: populates trails and allit
=cut
sub recurse_categories {
- my ($cats,$depth,$category,$trails,$allitems,$parents,$subcats) = @_;
+ my ($cats,$depth,$category,$trails,$allitems,$parents,$subcats,$maxd) = @_;
my $shallower = $depth - 1;
if (ref($cats->[$depth]{$category}) eq 'ARRAY') {
for (my $k=0; $k<@{$cats->[$depth]{$category}}; $k++) {
my $name = $cats->[$depth]{$category}[$k];
my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
- my $trailstr = join(' -> ',(@{$parents},$category));
+ my $trailstr = join(' » ',(@{$parents},$category));
if ($allitems->{$item} eq '') {
push(@{$trails},$trailstr);
$allitems->{$item} = scalar(@{$trails})-1;
@@ -13510,16 +15945,21 @@ sub recurse_categories {
}
}
&recurse_categories($cats,$deeper,$name,$trails,$allitems,$parents,
- $subcats);
+ $subcats,$maxd);
pop(@{$parents});
}
} else {
my $item = &escape($category).':'.&escape($parents->[-1]).':'.$shallower;
- my $trailstr = join(' -> ',(@{$parents},$category));
+ my $trailstr = join(' » ',(@{$parents},$category));
if ($allitems->{$item} eq '') {
push(@{$trails},$trailstr);
$allitems->{$item} = scalar(@{$trails})-1;
}
+ if (ref($maxd) eq 'HASH') {
+ if ($depth > $maxd->{$parents->[0]}) {
+ $maxd->{$parents->[0]} = $depth;
+ }
+ }
}
return;
}
@@ -13540,16 +15980,19 @@ currcat - scalar with an & separated lis
type - scalar contains course type (Course or Community).
+disabled - scalar (optional) contains disabled="disabled" if input elements are
+ to be readonly (e.g., Domain Helpdesk role viewing course settings).
+
Returns: $output (markup to be displayed)
=cut
sub assign_categories_table {
- my ($cathash,$currcat,$type) = @_;
+ my ($cathash,$currcat,$type,$disabled) = @_;
my $output;
if (ref($cathash) eq 'HASH') {
- my (@cats,@trails,%allitems,%idx,@jsarray,@path,$maxdepth);
- &extract_categories($cathash,\@cats,\@trails,\%allitems,\%idx,\@jsarray);
+ my (@cats,@trails,%allitems,%idx,@jsarray,%maxd,@path,$maxdepth);
+ &extract_categories($cathash,\@cats,\@trails,\%allitems,\%idx,\@jsarray,\%maxd);
$maxdepth = scalar(@cats);
if (@cats > 0) {
my $itemcount = 0;
@@ -13581,11 +16024,11 @@ sub assign_categories_table {
}
$table .= ''.
' '.$parent_title.' '.
+ $item.'"'.$checked.$disabled.' />'.$parent_title.''.
' ';
my $depth = 1;
push(@path,$parent);
- $table .= &assign_category_rows($itemcount,\@cats,$depth,$parent,\@path,\@currcategories);
+ $table .= &assign_category_rows($itemcount,\@cats,$depth,$parent,\@path,\@currcategories,$disabled);
pop(@path);
$table .= ' ';
$itemcount ++;
@@ -13624,12 +16067,15 @@ path - Array containing all categories b
currcategories - reference to array of current categories assigned to the course
+disabled - scalar (optional) contains disabled="disabled" if input elements are
+ to be readonly (e.g., Domain Helpdesk role viewing course settings).
+
Returns: $output (markup to be displayed).
=cut
sub assign_category_rows {
- my ($itemcount,$cats,$depth,$parent,$path,$currcategories) = @_;
+ my ($itemcount,$cats,$depth,$parent,$path,$currcategories,$disabled) = @_;
my ($text,$name,$item,$chgstr);
if (ref($cats) eq 'ARRAY') {
my $maxdepth = scalar(@{$cats});
@@ -13652,12 +16098,12 @@ sub assign_category_rows {
}
$text .= ''.
' '.$name.' '.
+ $item.'"'.$checked.$disabled.' />'.$name.''.
' '.
'';
if (ref($path) eq 'ARRAY') {
push(@{$path},$name);
- $text .= &assign_category_rows($itemcount,$cats,$deeper,$name,$path,$currcategories);
+ $text .= &assign_category_rows($itemcount,$cats,$deeper,$name,$path,$currcategories,$disabled);
pop(@{$path});
}
$text .= ' ';
@@ -13756,7 +16202,7 @@ sub commit_studentrole {
}
$oldsecurl = $uurl;
$expire_role_result =
- &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,'','',$context);
+ &Apache::lonnet::assignrole($udom,$uname,$uurl,'st',$now,'','','',$context);
if ($env{'request.course.sec'} ne '') {
if ($expire_role_result eq 'refused') {
my @roles = ('st');
@@ -13868,7 +16314,8 @@ sub check_clone {
my $cloneid='/'.$args->{'clonedomain'}.'/'.$args->{'clonecourse'};
my ($clonecrsudom,$clonecrsunum)= &LONCAPA::split_courseid($cloneid);
my $clonehome=&Apache::lonnet::homeserver($clonecrsunum,$clonecrsudom);
- my $clonemsg;
+ my $clonetitle;
+ my @clonemsg;
my $can_clone = 0;
my $lctype = lc($args->{'crstype'});
if ($lctype ne 'community') {
@@ -13876,59 +16323,154 @@ sub check_clone {
}
if ($clonehome eq 'no_host') {
if ($args->{'crstype'} eq 'Community') {
- $clonemsg = &mt('No new community created.').$linefeed.&mt('A new community could not be cloned from the specified original - [_1] - because it is a non-existent community.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
+ push(@clonemsg,({
+ mt => 'No new community created.',
+ args => [],
+ },
+ {
+ mt => 'A new community could not be cloned from the specified original - [_1] - because it is a non-existent community.',
+ args => [$args->{'clonedomain'}.':'.$args->{'clonedomain'}],
+ }));
} else {
- $clonemsg = &mt('No new course created.').$linefeed.&mt('A new course could not be cloned from the specified original - [_1] - because it is a non-existent course.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
- }
+ push(@clonemsg,({
+ mt => 'No new course created.',
+ args => [],
+ },
+ {
+ mt => 'A new course could not be cloned from the specified original - [_1] - because it is a non-existent course.',
+ args => [$args->{'clonecourse'}.':'.$args->{'clonedomain'}],
+ }));
+ }
} else {
my %clonedesc = &Apache::lonnet::coursedescription($cloneid,{'one_time' => 1});
+ $clonetitle = $clonedesc{'description'};
if ($args->{'crstype'} eq 'Community') {
if ($clonedesc{'type'} ne 'Community') {
- $clonemsg = &mt('No new community created.').$linefeed.&mt('A new community could not be cloned from the specified original - [_1] - because it is a course not a community.',$args->{'clonecourse'}.':'.$args->{'clonedomain'});
- return ($can_clone, $clonemsg, $cloneid, $clonehome);
+ push(@clonemsg,({
+ mt => 'No new community created.',
+ args => [],
+ },
+ {
+ mt => 'A new community could not be cloned from the specified original - [_1] - because it is a course not a community.',
+ args => [$args->{'clonecourse'}.':'.$args->{'clonedomain'}],
+ }));
+ return ($can_clone,\@clonemsg,$cloneid,$clonehome);
}
}
- if (($env{'request.role.domain'} eq $args->{'clonedomain'}) &&
+ if (($env{'request.role.domain'} eq $args->{'clonedomain'}) &&
(&Apache::lonnet::allowed('ccc',$env{'request.role.domain'}))) {
$can_clone = 1;
} else {
- my %clonehash = &Apache::lonnet::get('environment',['cloners'],
+ my %clonehash = &Apache::lonnet::get('environment',['cloners','internal.coursecode'],
$args->{'clonedomain'},$args->{'clonecourse'});
- my @cloners = split(/,/,$clonehash{'cloners'});
- if (grep(/^\*$/,@cloners)) {
- $can_clone = 1;
- } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
- $can_clone = 1;
+ if ($clonehash{'cloners'} eq '') {
+ my %domdefs = &Apache::lonnet::get_domain_defaults($args->{'course_domain'});
+ if ($domdefs{'canclone'}) {
+ unless ($domdefs{'canclone'} eq 'none') {
+ if ($domdefs{'canclone'} eq 'domain') {
+ if ($args->{'ccdomain'} eq $args->{'clonedomain'}) {
+ $can_clone = 1;
+ }
+ } elsif (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) &&
+ ($args->{'clonedomain'} eq $args->{'course_domain'})) {
+ if (&Apache::lonnet::default_instcode_cloning($args->{'clonedomain'},$domdefs{'canclone'},
+ $clonehash{'internal.coursecode'},$args->{'crscode'})) {
+ $can_clone = 1;
+ }
+ }
+ }
+ }
} else {
+ my @cloners = split(/,/,$clonehash{'cloners'});
+ if (grep(/^\*$/,@cloners)) {
+ $can_clone = 1;
+ } elsif (grep(/^\*\:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+ $can_clone = 1;
+ } elsif (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners)) {
+ $can_clone = 1;
+ }
+ unless ($can_clone) {
+ if (($clonehash{'internal.coursecode'}) && ($args->{'crscode'}) &&
+ ($args->{'clonedomain'} eq $args->{'course_domain'})) {
+ my (%gotdomdefaults,%gotcodedefaults);
+ foreach my $cloner (@cloners) {
+ if (($cloner ne '*') && ($cloner !~ /^\*\:$match_domain$/) &&
+ ($cloner !~ /^$match_username\:$match_domain$/) && ($cloner ne '')) {
+ my (%codedefaults,@code_order);
+ if (ref($gotcodedefaults{$args->{'clonedomain'}}) eq 'HASH') {
+ if (ref($gotcodedefaults{$args->{'clonedomain'}}{'defaults'}) eq 'HASH') {
+ %codedefaults = %{$gotcodedefaults{$args->{'clonedomain'}}{'defaults'}};
+ }
+ if (ref($gotcodedefaults{$args->{'clonedomain'}}{'order'}) eq 'ARRAY') {
+ @code_order = @{$gotcodedefaults{$args->{'clonedomain'}}{'order'}};
+ }
+ } else {
+ &Apache::lonnet::auto_instcode_defaults($args->{'clonedomain'},
+ \%codedefaults,
+ \@code_order);
+ $gotcodedefaults{$args->{'clonedomain'}}{'defaults'} = \%codedefaults;
+ $gotcodedefaults{$args->{'clonedomain'}}{'order'} = \@code_order;
+ }
+ if (@code_order > 0) {
+ if (&Apache::lonnet::check_instcode_cloning(\%codedefaults,\@code_order,
+ $cloner,$clonehash{'internal.coursecode'},
+ $args->{'crscode'})) {
+ $can_clone = 1;
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ unless ($can_clone) {
my $ccrole = 'cc';
if ($args->{'crstype'} eq 'Community') {
$ccrole = 'co';
}
- my %roleshash =
- &Apache::lonnet::get_my_roles($args->{'ccuname'},
- $args->{'ccdomain'},
- 'userroles',['active'],[$ccrole],
- [$args->{'clonedomain'}]);
- if (($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) || (grep(/^\Q$args->{'ccuname'}\E:\Q$args->{'ccdomain'}\E$/,@cloners))) {
+ my %roleshash =
+ &Apache::lonnet::get_my_roles($args->{'ccuname'},
+ $args->{'ccdomain'},
+ 'userroles',['active'],[$ccrole],
+ [$args->{'clonedomain'}]);
+ if ($roleshash{$args->{'clonecourse'}.':'.$args->{'clonedomain'}.':'.$ccrole}) {
$can_clone = 1;
- } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},$args->{'ccuname'},$args->{'ccdomain'})) {
+ } elsif (&Apache::lonnet::is_course_owner($args->{'clonedomain'},$args->{'clonecourse'},
+ $args->{'ccuname'},$args->{'ccdomain'})) {
$can_clone = 1;
+ }
+ }
+ unless ($can_clone) {
+ if ($args->{'crstype'} eq 'Community') {
+ push(@clonemsg,({
+ mt => 'No new community created.',
+ args => [],
+ },
+ {
+ mt => 'The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',
+ args => [$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'}],
+ }));
} else {
- if ($args->{'crstype'} eq 'Community') {
- $clonemsg = &mt('No new community created.').$linefeed.&mt('The new community could not be cloned from the existing community because the new community owner ([_1]) does not have cloning rights in the existing community ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
- } else {
- $clonemsg = &mt('No new course created.').$linefeed.&mt('The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'});
- }
+ push(@clonemsg,({
+ mt => 'No new course created.',
+ args => [],
+ },
+ {
+ mt => 'The new course could not be cloned from the existing course because the new course owner ([_1]) does not have cloning rights in the existing course ([_2]).',
+ args => [$args->{'ccuname'}.':'.$args->{'ccdomain'},$clonedesc{'description'}],
+ }));
}
}
}
}
- return ($can_clone, $clonemsg, $cloneid, $clonehome);
+ return ($can_clone,\@clonemsg,$cloneid,$clonehome,$clonetitle);
}
sub construct_course {
- my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category,$coderef) = @_;
- my $outcome;
+ my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,
+ $cnum,$category,$coderef,$callercontext,$user_lh) = @_;
+ my ($outcome,$msgref,$clonemsgref);
my $linefeed = ' '."\n";
if ($context eq 'auto') {
$linefeed = "\n";
@@ -13937,18 +16479,11 @@ sub construct_course {
#
# Are we cloning?
#
- my ($can_clone, $clonemsg, $cloneid, $clonehome);
+ my ($can_clone,$cloneid,$clonehome,$clonetitle);
if (($args->{'clonecourse'}) && ($args->{'clonedomain'})) {
- ($can_clone, $clonemsg, $cloneid, $clonehome) = &check_clone($args,$linefeed);
- if ($context ne 'auto') {
- if ($clonemsg ne '') {
- $clonemsg = ''.$clonemsg.' ';
- }
- }
- $outcome .= $clonemsg.$linefeed;
-
+ ($can_clone,$clonemsgref,$cloneid,$clonehome,$clonetitle) = &check_clone($args,$linefeed);
if (!$can_clone) {
- return (0,$outcome);
+ return (0,$outcome,$clonemsgref);
}
}
@@ -13966,15 +16501,20 @@ sub construct_course {
$args->{'ccuname'}.':'.
$args->{'ccdomain'},
$args->{'crstype'},
- $cnum,$context,$category);
+ $cnum,$context,$category,
+ $callercontext);
# Note: The testing routines depend on this being output; see
# Utils::Course. This needs to at least be output as a comment
# if anyone ever decides to not show this, and Utils::Course::new
# will need to be suitably modified.
- $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$crstype,$$courseid).$linefeed;
+ if (($callercontext eq 'auto') && ($user_lh ne '')) {
+ $outcome .= &mt_user($user_lh,'New LON-CAPA [_1] ID: [_2]',$crstype,$$courseid).$linefeed;
+ } else {
+ $outcome .= &mt('New LON-CAPA [_1] ID: [_2]',$crstype,$$courseid).$linefeed;
+ }
if ($$courseid =~ /^error:/) {
- return (0,$outcome);
+ return (0,$outcome,$clonemsgref);
}
#
@@ -13983,23 +16523,37 @@ sub construct_course {
($$crsudom,$$crsunum)= &LONCAPA::split_courseid($$courseid);
my $crsuhome=&Apache::lonnet::homeserver($$crsunum,$$crsudom);
if ($crsuhome eq 'no_host') {
- $outcome .= &mt('Course creation failed, unrecognized course home server.').$linefeed;
- return (0,$outcome);
+ if (($callercontext eq 'auto') && ($user_lh ne '')) {
+ $outcome .= &mt_user($user_lh,
+ 'Course creation failed, unrecognized course home server.');
+ } else {
+ $outcome .= &mt('Course creation failed, unrecognized course home server.');
+ }
+ $outcome .= $linefeed;
+ return (0,$outcome,$clonemsgref);
}
$outcome .= &mt('Created on').': '.$crsuhome.$linefeed;
#
# Do the cloning
-#
+#
+ my @clonemsg;
if ($can_clone && $cloneid) {
- $clonemsg = &mt('Cloning [_1] from [_2]',$crstype,$clonehome);
- if ($context ne 'auto') {
- $clonemsg = ''.$clonemsg.' ';
- }
- $outcome .= $clonemsg.$linefeed;
+ push(@clonemsg,
+ {
+ mt => 'Created [_1] by cloning from [_2]',
+ args => [$crstype,$clonetitle],
+ });
my %oldcenv=&Apache::lonnet::dump('environment',$$crsudom,$$crsunum);
# Copy all files
- &Apache::lonclonecourse::copycoursefiles($cloneid,$$courseid,$args->{'datemode'},$args->{'dateshift'});
+ my @info =
+ &Apache::lonclonecourse::copycoursefiles($cloneid,$$courseid,$args->{'datemode'},
+ $args->{'dateshift'},$args->{'crscode'},
+ $args->{'ccuname'}.':'.$args->{'ccdomain'},
+ $args->{'tinyurls'});
+ if (@info) {
+ push(@clonemsg,@info);
+ }
# Restore URL
$cenv{'url'}=$oldcenv{'url'};
# Restore title
@@ -14024,8 +16578,7 @@ sub construct_course {
'plc.users.denied',
'hidefromcat',
'checkforpriv',
- 'categories',
- 'internal.uniquecode'],
+ 'categories'],
$$crsudom,$$crsunum);
if ($args->{'textbook'}) {
$cenv{'internal.textbook'} = $args->{'textbook'};
@@ -14040,6 +16593,9 @@ sub construct_course {
if ($args->{'crstype'}) {
$cenv{'type'}=$args->{'crstype'};
}
+ if ($args->{'lti'}) {
+ $cenv{'internal.lti'}=$args->{'lti'};
+ }
if ($args->{'crsid'}) {
$cenv{'courseid'}=$args->{'crsid'};
}
@@ -14061,6 +16617,7 @@ sub construct_course {
$cenv{'internal.defaultcredits'} = $args->{'defaultcredits'};
}
my @badclasses = (); # Used to accumulate sections/crosslistings that did not pass classlist access check for course owner.
+ my @oklcsecs = (); # Used to accumulate LON-CAPA sections for validated institutional sections.
if ($args->{'crssections'}) {
$cenv{'internal.sectionnums'} = '';
if ($args->{'crssections'} =~ m/,/) {
@@ -14074,8 +16631,12 @@ sub construct_course {
my $class = $args->{'crscode'}.$sec;
my $addcheck = &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$class,$cenv{'internal.courseowner'});
$cenv{'internal.sectionnums'} .= $item.',';
- unless ($addcheck eq 'ok') {
- push @badclasses, $class;
+ if ($addcheck eq 'ok') {
+ unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+ push(@oklcsecs,$gp);
+ }
+ } else {
+ push(@badclasses,$class);
}
}
$cenv{'internal.sectionnums'} =~ s/,$//;
@@ -14102,8 +16663,12 @@ sub construct_course {
my ($xl,$gp) = split/:/,$item;
my $addcheck = &Apache::lonnet::auto_new_course($$crsunum,$$crsudom,$xl,$cenv{'internal.courseowner'});
$cenv{'internal.crosslistings'} .= $item.',';
- unless ($addcheck eq 'ok') {
- push @badclasses, $xl;
+ if ($addcheck eq 'ok') {
+ unless (grep(/^\Q$gp\E$/,@oklcsecs)) {
+ push(@oklcsecs,$gp);
+ }
+ } else {
+ push(@badclasses,$xl);
}
}
$cenv{'internal.crosslistings'} =~ s/,$//;
@@ -14138,32 +16703,63 @@ sub construct_course {
}
if (@badclasses > 0) {
my %lt=&Apache::lonlocal::texthash(
- 'tclb' => 'The courses listed below were included as sections or crosslistings affiliated with your new LON-CAPA course. However, if automated course roster updates are enabled for this class, these particular sections/crosslistings will not contribute towards enrollment, because the user identified as the course owner for this LON-CAPA course',
- 'dnhr' => 'does not have rights to access enrollment in these classes',
- 'adby' => 'as determined by the policies of your institution on access to official classlists'
+ 'tclb' => 'The courses listed below were included as sections or crosslistings affiliated with your new LON-CAPA course.',
+ 'howi' => 'However, if automated course roster updates are enabled for this class, these particular sections/crosslistings are not guaranteed to contribute towards enrollment.',
+ 'itis' => 'It is possible that rights to access enrollment for these classes will be available through assignment of co-owners.',
);
- my $badclass_msg = $cenv{'internal.courseowner'}.') - '.$lt{'dnhr'}.
- ' ('.$lt{'adby'}.')';
+ my $badclass_msg = $lt{'tclb'}.$linefeed.$lt{'howi'}.$linefeed.
+ &mt('That is because the user identified as the course owner ([_1]) does not have rights to access enrollment in these classes, as determined by the policies of your institution on access to official classlists',$cenv{'internal.courseowner'}).$linefeed.$lt{'itis'};
if ($context eq 'auto') {
$outcome .= $badclass_msg.$linefeed;
+ } else {
$outcome .= ''.$badclass_msg.$linefeed.'
'."\n";
- foreach my $item (@badclasses) {
- if ($context eq 'auto') {
- $outcome .= " - $item\n";
- } else {
- $outcome .= "$item \n";
- }
- }
+ }
+ foreach my $item (@badclasses) {
if ($context eq 'auto') {
- $outcome .= $linefeed;
+ $outcome .= " - $item\n";
} else {
- $outcome .= " \n";
+ $outcome .= "$item \n";
}
- }
+ }
+ if ($context eq 'auto') {
+ $outcome .= $linefeed;
+ } else {
+ $outcome .= " \n";
+ }
}
if ($args->{'no_end_date'}) {
$args->{'endaccess'} = 0;
}
+# If an official course with institutional sections is created by cloning
+# an existing course, section-specific hiding of course totals in student's
+# view of grades as copied from cloned course, will be checked for valid
+# sections.
+ if (($can_clone && $cloneid) &&
+ ($cenv{'internal.coursecode'} ne '') &&
+ ($cenv{'grading'} eq 'standard') &&
+ ($cenv{'hidetotals'} ne '') &&
+ ($cenv{'hidetotals'} ne 'all')) {
+ my @hidesecs;
+ my $deletehidetotals;
+ if (@oklcsecs) {
+ foreach my $sec (split(/,/,$cenv{'hidetotals'})) {
+ if (grep(/^\Q$sec$/,@oklcsecs)) {
+ push(@hidesecs,$sec);
+ }
+ }
+ if (@hidesecs) {
+ $cenv{'hidetotals'} = join(',',@hidesecs);
+ } else {
+ $deletehidetotals = 1;
+ }
+ } else {
+ $deletehidetotals = 1;
+ }
+ if ($deletehidetotals) {
+ delete($cenv{'hidetotals'});
+ &Apache::lonnet::del('environment',['hidetotals'],$$crsudom,$$crsunum);
+ }
+ }
$cenv{'internal.autostart'}=$args->{'enrollstart'};
$cenv{'internal.autoend'}=$args->{'enrollend'};
$cenv{'default_enrollment_start_date'}=$args->{'startaccess'};
@@ -14191,6 +16787,9 @@ sub construct_course {
if ($args->{'setcontent'}) {
$cenv{'question.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
}
+ if ($args->{'setcomment'}) {
+ $cenv{'comment.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
+ }
}
if ($args->{'reshome'}) {
$cenv{'reshome'}=$args->{'reshome'}.'/';
@@ -14262,19 +16861,23 @@ sub construct_course {
# Open all assignments
#
if ($args->{'openall'}) {
+ my $opendate = time;
+ if ($args->{'openallfrom'} =~ /^\d+$/) {
+ $opendate = $args->{'openallfrom'};
+ }
my $storeunder=$$crsudom.'_'.$$crsunum.'.0.opendate';
- my %storecontent = ($storeunder => time,
+ my %storecontent = ($storeunder => $opendate,
$storeunder.'.type' => 'date_start');
-
- $outcome .= &mt('Opening all assignments').': '.&Apache::lonnet::cput
- ('resourcedata',\%storecontent,$$crsudom,$$crsunum).$linefeed;
+ $outcome .= &mt('All assignments open starting [_1]',
+ &Apache::lonlocal::locallocaltime($opendate)).': '.
+ &Apache::lonnet::cput
+ ('resourcedata',\%storecontent,$$crsudom,$$crsunum).$linefeed;
}
#
# Set first page
#
unless (($args->{'nonstandard'}) || ($args->{'firstres'} eq 'blank')
|| ($cloneid)) {
- use LONCAPA::map;
$outcome .= &mt('Setting first resource').': ';
my $map = '/uploaded/'.$$crsudom.'/'.$$crsunum.'/default.sequence';
@@ -14297,7 +16900,7 @@ sub construct_course {
$outcome .= ($fatal?$errtext:'write ok').$linefeed;
}
- return (1,$outcome);
+ return (1,$outcome,\@clonemsg);
}
sub make_unique_code {
@@ -14381,12 +16984,13 @@ sub group_term {
}
sub course_types {
- my @types = ('official','unofficial','community','textbook');
+ my @types = ('official','unofficial','community','textbook','lti');
my %typename = (
official => 'Official course',
unofficial => 'Unofficial course',
community => 'Community',
textbook => 'Textbook course',
+ lti => 'LTI provider',
);
return (\@types,\%typename);
}
@@ -14447,7 +17051,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 {
@@ -14466,6 +17070,24 @@ sub compare_arrays {
return @difference;
}
+sub lon_status_items {
+ my %defaults = (
+ E => 100,
+ W => 4,
+ N => 1,
+ U => 5,
+ threshold => 200,
+ sysmail => 2500,
+ );
+ my %names = (
+ E => 'Errors',
+ W => 'Warnings',
+ N => 'Notices',
+ U => 'Unsent',
+ );
+ return (\%defaults,\%names);
+}
+
# -------------------------------------------------------- Initialize user login
sub init_user_environment {
my ($r, $username, $domain, $authhost, $form, $args) = @_;
@@ -14475,7 +17097,8 @@ sub init_user_environment {
# See if old ID present, if so, remove
- my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
+ my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv,
+ $coauthorenv);
my $now=time;
if ($public) {
@@ -14501,10 +17124,37 @@ sub init_user_environment {
opendir(DIR,$lonids);
while ($filename=readdir(DIR)) {
if ($filename=~/^$username\_\d+\_$domain\_$authhost\.id$/) {
- unlink($lonids.'/'.$filename);
+ if (tie(my %oldenv,'GDBM_File',"$lonids/$filename",
+ &GDBM_READER(),0640)) {
+ my $linkedfile;
+ if (exists($oldenv{'user.linkedenv'})) {
+ $linkedfile = $oldenv{'user.linkedenv'};
+ }
+ untie(%oldenv);
+ if (unlink("$lonids/$filename")) {
+ if ($linkedfile =~ /^[a-f0-9]+_linked$/) {
+ if (-l "$lonids/$linkedfile.id") {
+ unlink("$lonids/$linkedfile.id");
+ }
+ }
+ }
+ } else {
+ unlink($lonids.'/'.$filename);
+ }
}
}
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'}
@@ -14513,13 +17163,13 @@ sub init_user_environment {
# Initialize roles
- ($userroles,$firstaccenv,$timerintenv) =
+ ($userroles,$firstaccenv,$timerintenv,$coauthorenv) =
&Apache::lonnet::rolesinit($domain,$username,$authhost);
}
# ------------------------------------ Check browser type and MathML capability
- my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
- $clientunicode,$clientos,$clientmobile,$clientinfo) = &decode_user_agent($r);
+ my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,$clientunicode,
+ $clientos,$clientmobile,$clientinfo,$clientosversion) = &decode_user_agent($r);
# ------------------------------------------------------------- Get environment
@@ -14541,6 +17191,7 @@ sub init_user_environment {
# --------------------------------------------------------- Write first profile
{
+ my $ip = &Apache::lonnet::get_requestor_ip();
my %initial_env =
("user.name" => $username,
"user.domain" => $domain,
@@ -14552,13 +17203,14 @@ sub init_user_environment {
"browser.os" => $clientos,
"browser.mobile" => $clientmobile,
"browser.info" => $clientinfo,
+ "browser.osversion" => $clientosversion,
"server.domain" => $Apache::lonnet::perlvar{'lonDefDomain'},
"request.course.fn" => '',
"request.course.uri" => '',
"request.course.sec" => '',
"request.role" => 'cm',
"request.role.adv" => $env{'user.adv'},
- "request.host" => $ENV{'REMOTE_ADDR'},);
+ "request.host" => $ip,);
if ($form->{'localpath'}) {
$initial_env{"browser.localpath"} = $form->{'localpath'};
@@ -14577,36 +17229,55 @@ sub init_user_environment {
$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);
+ if ($form->{'noloadbalance'}) {
+ my @hosts = &Apache::lonnet::current_machine_ids();
+ my $hosthere = $form->{'noloadbalance'};
+ if (grep(/^\Q$hosthere\E$/,@hosts)) {
+ $initial_env{"user.noloadbalance"} = $hosthere;
+ $env{'user.noloadbalance'} = $hosthere;
+ }
}
- foreach my $tool ('aboutme','blog','webdav','portfolio') {
- $userenv{'availabletools.'.$tool} =
- &Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
- undef,\%userenv,\%domdef,\%is_adv);
- }
+ unless ($domain eq 'public') {
+ my %is_adv = ( is_adv => $env{'user.adv'} );
+ my %domdef = &Apache::lonnet::get_domain_defaults($domain);
- foreach my $crstype ('official','unofficial','community','textbook') {
- $userenv{'canrequest.'.$crstype} =
- &Apache::lonnet::usertools_access($username,$domain,$crstype,
- 'reload','requestcourses',
- \%userenv,\%domdef,\%is_adv);
- }
+ foreach my $tool ('aboutme','blog','webdav','portfolio','timezone') {
+ $userenv{'availabletools.'.$tool} =
+ &Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
+ undef,\%userenv,\%domdef,\%is_adv);
+ }
+
+ foreach my $crstype ('official','unofficial','community','textbook','lti') {
+ $userenv{'canrequest.'.$crstype} =
+ &Apache::lonnet::usertools_access($username,$domain,$crstype,
+ 'reload','requestcourses',
+ \%userenv,\%domdef,\%is_adv);
+ }
+
+ if ((ref($userroles) eq 'HASH') && ($userroles->{'user.author'}) &&
+ (exists($userroles->{"user.role.au./$domain/"}))) {
+ if ($userenv{'authoreditors'}) {
+ $userenv{'editors'} = $userenv{'authoreditors'};
+ } elsif ($domdef{'editors'} ne '') {
+ $userenv{'editors'} = $domdef{'editors'};
+ } else {
+ $userenv{'editors'} = 'edit,xml';
+ }
+ }
- $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'};
+ $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'};
+ }
}
}
@@ -14623,6 +17294,11 @@ sub init_user_environment {
if (ref($timerintenv) eq 'HASH') {
&_add_to_env(\%disk_env,$timerintenv);
}
+ if (ref($coauthorenv) eq 'HASH') {
+ if (keys(%{$coauthorenv})) {
+ &_add_to_env(\%disk_env,$coauthorenv);
+ }
+ }
if (ref($args->{'extra_env'})) {
&_add_to_env(\%disk_env,$args->{'extra_env'});
}
@@ -14922,7 +17598,12 @@ sub build_filters {
$output .= ' '."\n".
' '."\n";
- } elsif ($formname ne 'quotacheck') {
+ } elsif ($formname eq 'quotacheck') {
+ $output .= qq|
+
+
+|;
+ } else {
my $name_input;
if ($cnameelement ne '') {
$name_input = ' {'ownerfilter'} ne '') ||
($filter->{'ownerdomfilter'} ne '')) {
@@ -15178,10 +17867,10 @@ sub search_courses {
$filter->{'combownerfilter'},
$filter->{'coursefilter'},
undef,undef,$type,$regexpok,undef,undef,
- undef,undef,$cloner,$env{'form.cc_clone'},
+ undef,undef,$cloner,$cc_clone,
$filter->{'cloneableonly'},
$createdbefore,$createdafter,undef,
- $domcloner);
+ $domcloner,undef,$reqcrsdom,$reqinstcode);
if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) {
my $ccrole;
if ($type eq 'Community') {
@@ -15201,7 +17890,7 @@ sub search_courses {
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);
+ push(@{$courses{$cid}{roles}},$courserole);
}
} else {
$courses{$cid}{roles} = [$courserole];
@@ -15215,42 +17904,237 @@ sub search_courses {
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'});
-sub build_release_hashes {
- my ($checkparms,$checkresponsetypes,$checkcrstypes,$anonsurvey,$randomizetry) = @_;
- return unless((ref($checkparms) eq 'HASH') && (ref($checkresponsetypes) eq 'HASH') &&
- (ref($checkcrstypes) eq 'HASH') && (ref($anonsurvey) eq 'HASH') &&
- (ref($randomizetry) eq 'HASH'));
- foreach my $key (keys(%Apache::lonnet::needsrelease)) {
- my ($item,$name,$value) = split(/:/,$key);
- if ($item eq 'parameter') {
- if (ref($checkparms->{$name}) eq 'ARRAY') {
- unless(grep(/^\Q$name\E$/,@{$checkparms->{$name}})) {
- push(@{$checkparms->{$name}},$value);
+ 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;
}
- } else {
- push(@{$checkparms->{$name}},$value);
}
- } elsif ($item eq 'resourcetag') {
- if ($name eq 'responsetype') {
- $checkresponsetypes->{$value} = $Apache::lonnet::needsrelease{$key}
+ if ($otherserver ne '') {
+ $switchserver = 'otherserver='.$otherserver.'&role='.$rolecode;
}
- } elsif ($item eq 'course') {
- if ($name eq 'crstype') {
- $checkcrstypes->{$value} = $Apache::lonnet::needsrelease{$key};
+ }
+ }
+ 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) {
+ &Apache::lonnet::appenv({'request.course.timechecked'=>$now});
+ my $blocked = &blocking_status('reinit',undef,$cnum,$cdom,undef,1);
+ if ($blocked) {
+ return ();
+ }
+ my $update;
+ my $lastmainchange = &Apache::lonnet::get_coursechange($cdom,$cnum);
+ my $lastsuppchange = &Apache::lonnet::get_suppchange($cdom,$cnum);
+ if ($lastmainchange > $env{'request.course.tied'}) {
+ my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+ if ($needswitch) {
+ return ('switch',$switchwarning,$switchserver);
+ }
+ $update = 'main';
+ }
+ if ($lastsuppchange > $env{'request.course.suppupdated'}) {
+ if ($update) {
+ $update = 'both';
+ } else {
+ my ($needswitch,$switchwarning,$switchserver) = &switch_for_update($loncaparev,$cdom,$cnum);
+ if ($needswitch) {
+ return ('switch',$switchwarning,$switchserver);
+ } else {
+ $update = 'supp';
+ }
}
+ return ($update);
}
}
- ($anonsurvey->{major},$anonsurvey->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:anonsurvey'});
- ($randomizetry->{major},$randomizetry->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:randomizetry'});
- return;
+ return ();
+}
+
+sub switch_for_update {
+ my ($loncaparev,$cdom,$cnum) = @_;
+ 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 ();
}
sub update_content_constraints {
@@ -15330,8 +18214,10 @@ sub parse_supplemental_title {
my $name = &plainname($uname,$udom);
$name = &HTML::Entities::encode($name,'"<>&\'');
$renametitle = &HTML::Entities::encode($renametitle,'"<>&\'');
- $title=''.&Apache::lonlocal::locallocaltime($time).' '.
- $name.': '.$foldertitle;
+ $title=''.&Apache::lonlocal::locallocaltime($time).' '.$name;
+ if ($foldertitle ne '') {
+ $title .= ': '.$foldertitle;
+ }
}
if (wantarray) {
return ($title,$foldertitle,$renametitle);
@@ -15339,33 +18225,152 @@ sub parse_supplemental_title {
return $title;
}
+sub get_supplemental {
+ my ($cnum,$cdom,$ignorecache,$possdel)=@_;
+ my $hashid=$cnum.':'.$cdom;
+ my ($supplemental,$cached,$set_httprefs);
+ unless ($ignorecache) {
+ ($supplemental,$cached) = &Apache::lonnet::is_cached_new('supplemental',$hashid);
+ }
+ unless (defined($cached)) {
+ my $chome=&Apache::lonnet::homeserver($cnum,$cdom);
+ unless ($chome eq 'no_host') {
+ my @order = @LONCAPA::map::order;
+ my @resources = @LONCAPA::map::resources;
+ my @resparms = @LONCAPA::map::resparms;
+ my @zombies = @LONCAPA::map::zombies;
+ my ($errors,%ids,%hidden);
+ $errors =
+ &recurse_supplemental($cnum,$cdom,'supplemental.sequence',
+ $errors,$possdel,\%ids,\%hidden);
+ @LONCAPA::map::order = @order;
+ @LONCAPA::map::resources = @resources;
+ @LONCAPA::map::resparms = @resparms;
+ @LONCAPA::map::zombies = @zombies;
+ $set_httprefs = 1;
+ if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+ &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+ }
+ $supplemental = {
+ ids => \%ids,
+ hidden => \%hidden,
+ };
+ &Apache::lonnet::do_cache_new('supplemental',$hashid,$supplemental,600);
+ }
+ }
+ return ($supplemental,$set_httprefs);
+}
+
sub recurse_supplemental {
- my ($cnum,$cdom,$suppmap,$numfiles,$errors) = @_;
- if ($suppmap) {
+ my ($cnum,$cdom,$suppmap,$errors,$possdel,$suppids,$hiddensupp,$hidden) = @_;
+ if (($suppmap) && (ref($suppids) eq 'HASH') && (ref($hiddensupp) eq 'HASH')) {
+ my $mapnum;
+ if ($suppmap eq 'supplemental.sequence') {
+ $mapnum = 0;
+ } else {
+ ($mapnum) = ($suppmap =~ /^supplemental_(\d+)\.sequence$/);
+ }
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);
+ my @order = @LONCAPA::map::order;
+ if (@order > 0) {
+ my @resources = @LONCAPA::map::resources;
+ my @resparms = @LONCAPA::map::resparms;
+ foreach my $idx (@order) {
+ my ($title,$src,$ext,$type,$status)=split(/\:/,$resources[$idx]);
if (($src ne '') && ($status eq 'res')) {
+ my $id = $mapnum.':'.$idx;
+ push(@{$suppids->{$src}},$id);
+ if (($hidden) || (&get_supp_parameter($resparms[$idx],'parameter_hiddenresource') =~ /^yes/i)) {
+ $hiddensupp->{$id} = 1;
+ }
if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
- ($numfiles,$errors) = &recurse_supplemental($cnum,$cdom,$1,$numfiles,$errors);
+ $errors = &recurse_supplemental($cnum,$cdom,$1,$errors,$possdel,$suppids,
+ $hiddensupp,$hiddensupp->{$id});
} else {
- $numfiles ++;
+ my $allowed;
+ if (($env{'request.role.adv'}) || (!$hiddensupp->{$id})) {
+ $allowed = 1;
+ } elsif ($possdel) {
+ foreach my $item (@{$suppids->{$src}}) {
+ next if ($item eq $id);
+ unless ($hiddensupp->{$item}) {
+ $allowed = 1;
+ last;
+ }
+ }
+ if ((!$allowed) && (exists($env{'httpref.'.$src}))) {
+ &Apache::lonnet::delenv('httpref.'.$src);
+ }
+ }
+ if ($allowed && (!exists($env{'httpref.'.$src}))) {
+ &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
+ }
}
}
}
}
}
}
- return ($numfiles,$errors);
+ return $errors;
+}
+
+sub set_supp_httprefs {
+ my ($cnum,$cdom,$supplemental,$possdel) = @_;
+ if (ref($supplemental) eq 'HASH') {
+ if ((ref($supplemental->{'ids'}) eq 'HASH') && (ref($supplemental->{'hidden'}) eq 'HASH')) {
+ foreach my $src (keys(%{$supplemental->{'ids'}})) {
+ next if ($src =~ /\.sequence$/);
+ if (ref($supplemental->{'ids'}->{$src}) eq 'ARRAY') {
+ my $allowed;
+ if ($env{'request.role.adv'}) {
+ $allowed = 1;
+ } else {
+ foreach my $id (@{$supplemental->{'ids'}->{$src}}) {
+ unless ($supplemental->{'hidden'}->{$id}) {
+ $allowed = 1;
+ last;
+ }
+ }
+ }
+ if (exists($env{'httpref.'.$src})) {
+ if ($possdel) {
+ unless ($allowed) {
+ &Apache::lonnet::delenv('httpref.'.$src);
+ }
+ }
+ } elsif ($allowed) {
+ &Apache::lonnet::allowuploaded('/adm/coursedoc',$src);
+ }
+ }
+ }
+ if ($env{'request.course.id'} eq $cdom.'_'.$cnum) {
+ &Apache::lonnet::appenv({'request.course.suppupdated' => time});
+ }
+ }
+ }
+}
+
+sub get_supp_parameter {
+ my ($resparm,$name)=@_;
+ return if ($resparm eq '');
+ my $value=undef;
+ my $ptype=undef;
+ foreach (split('&&&',$resparm)) {
+ my ($thistype,$thisname,$thisvalue)=split('___',$_);
+ if ($thisname eq $name) {
+ $value=$thisvalue;
+ $ptype=$thistype;
+ }
+ }
+ return $value;
}
sub symb_to_docspath {
- my ($symb) = @_;
- return unless ($symb);
+ my ($symb,$navmapref) = @_;
+ return unless ($symb && ref($navmapref));
my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb);
if ($resurl=~/\.(sequence|page)$/) {
$mapurl=$resurl;
@@ -15373,9 +18378,11 @@ sub symb_to_docspath {
$mapurl=$env{'course.'.$env{'request.course.id'}.'.url'};
}
my $mapresobj;
- my $navmap = Apache::lonnavmaps::navmap->new();
- if (ref($navmap)) {
- $mapresobj = $navmap->getResourceByUrl($mapurl);
+ unless (ref($$navmapref)) {
+ $$navmapref = Apache::lonnavmaps::navmap->new();
+ }
+ if (ref($$navmapref)) {
+ $mapresobj = $$navmapref->getResourceByUrl($mapurl);
}
$mapurl=~s{^.*/([^/]+)\.(\w+)$}{$1};
my $type=$2;
@@ -15385,7 +18392,7 @@ sub symb_to_docspath {
if ($pcslist ne '') {
foreach my $pc (split(/,/,$pcslist)) {
next if ($pc <= 1);
- my $res = $navmap->getByMapPc($pc);
+ my $res = $$navmapref->getByMapPc($pc);
if (ref($res)) {
my $thisurl = $res->src();
$thisurl=~s{^.*/([^/]+)\.\w+$}{$1};
@@ -15431,32 +18438,94 @@ sub symb_to_docspath {
return $path;
}
+sub validate_folderpath {
+ my ($supplementalflag,$allowed,$coursenum,$coursedom) = @_;
+ if ($env{'form.folderpath'} ne '') {
+ my @items = split(/\&/,$env{'form.folderpath'});
+ my ($badpath,$changed,$got_supp,$supppath,%supphidden,%suppids);
+ for (my $i=0; $i<@items; $i++) {
+ my $odd = $i%2;
+ if (($odd) && (!$supplementalflag) && ($items[$i] !~ /^[^:]*:(|\d+):(|1):(|1):(|1):(|1)$/)) {
+ $badpath = 1;
+ } elsif ($odd && $supplementalflag) {
+ my $idx = $i-1;
+ if ($items[$i] =~ /^([^:]*)::(|1):::$/) {
+ my $esc_name = $1;
+ if ((!$allowed) || ($items[$idx] eq 'supplemental')) {
+ $supppath .= '&'.$esc_name;
+ $changed = 1;
+ } else {
+ $supppath .= '&'.$items[$i];
+ }
+ } elsif (($allowed) && ($items[$idx] ne 'supplemental')) {
+ $changed = 1;
+ my $is_hidden;
+ unless ($got_supp) {
+ my ($supplemental) = &get_supplemental($coursenum,$coursedom);
+ if (ref($supplemental) eq 'HASH') {
+ if (ref($supplemental->{'hidden'}) eq 'HASH') {
+ %supphidden = %{$supplemental->{'hidden'}};
+ }
+ if (ref($supplemental->{'ids'}) eq 'HASH') {
+ %suppids = %{$supplemental->{'ids'}};
+ }
+ }
+ $got_supp = 1;
+ }
+ if (ref($suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}) eq 'ARRAY') {
+ my $mapid = $suppids{"/uploaded/$coursedom/$coursenum/$items[$idx].sequence"}->[0];
+ if ($supphidden{$mapid}) {
+ $is_hidden = 1;
+ }
+ }
+ $supppath .= '&'.$items[$i].'::'.$is_hidden.':::';
+ } else {
+ $supppath .= '&'.$items[$i];
+ }
+ } elsif ((!$odd) && ($items[$i] !~ /^(default|supplemental)(|_\d+)$/)) {
+ $badpath = 1;
+ } elsif ($supplementalflag) {
+ $supppath .= '&'.$items[$i];
+ }
+ last if ($badpath);
+ }
+ if ($badpath) {
+ delete($env{'form.folderpath'});
+ } elsif ($changed && $supplementalflag) {
+ $supppath =~ s/^\&//;
+ $env{'form.folderpath'} = $supppath;
+ }
+ }
+ return;
+}
+
sub captcha_display {
- my ($context,$lonhost) = @_;
+ my ($context,$lonhost,$defdom) = @_;
my ($output,$error);
- my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ my ($captcha,$pubkey,$privkey,$version) =
+ &get_captcha_config($context,$lonhost,$defdom);
if ($captcha eq 'original') {
$output = &create_captcha();
unless ($output) {
$error = 'captcha';
}
} elsif ($captcha eq 'recaptcha') {
- $output = &create_recaptcha($pubkey);
+ $output = &create_recaptcha($pubkey,$version);
unless ($output) {
$error = 'recaptcha';
}
}
- return ($output,$error,$captcha);
+ return ($output,$error,$captcha,$version);
}
sub captcha_response {
- my ($context,$lonhost) = @_;
+ my ($context,$lonhost,$defdom) = @_;
my ($captcha_chk,$captcha_error);
- my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost,$defdom);
if ($captcha eq 'original') {
($captcha_chk,$captcha_error) = &check_captcha();
} elsif ($captcha eq 'recaptcha') {
- $captcha_chk = &check_recaptcha($privkey);
+ $captcha_chk = &check_recaptcha($privkey,$version);
} else {
$captcha_chk = 1;
}
@@ -15464,8 +18533,8 @@ sub captcha_response {
}
sub get_captcha_config {
- my ($context,$lonhost) = @_;
- my ($captcha,$pubkey,$privkey,$hashtocheck);
+ my ($context,$lonhost,$dom_in_effect) = @_;
+ my ($captcha,$pubkey,$privkey,$version,$hashtocheck);
my $hostname = &Apache::lonnet::hostname($lonhost);
my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
@@ -15481,6 +18550,10 @@ sub get_captcha_config {
}
if ($privkey && $pubkey) {
$captcha = 'recaptcha';
+ $version = $hashtocheck->{'recaptchaversion'};
+ if ($version ne '2') {
+ $version = 1;
+ }
} else {
$captcha = 'original';
}
@@ -15498,14 +18571,39 @@ sub get_captcha_config {
$privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'};
if ($privkey && $pubkey) {
$captcha = 'recaptcha';
+ $version = $domconfhash{$serverhomedom.'.login.recaptchaversion'};
+ if ($version ne '2') {
+ $version = 1;
+ }
} else {
$captcha = 'original';
}
} elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
$captcha = 'original';
}
+ } elsif ($context eq 'passwords') {
+ if ($dom_in_effect) {
+ my %passwdconf = &Apache::lonnet::get_passwdconf($dom_in_effect);
+ if ($passwdconf{'captcha'} eq 'recaptcha') {
+ if (ref($passwdconf{'recaptchakeys'}) eq 'HASH') {
+ $pubkey = $passwdconf{'recaptchakeys'}{'public'};
+ $privkey = $passwdconf{'recaptchakeys'}{'private'};
+ }
+ if ($privkey && $pubkey) {
+ $captcha = 'recaptcha';
+ $version = $passwdconf{'recaptchaversion'};
+ if ($version ne '2') {
+ $version = 1;
+ }
+ } else {
+ $captcha = 'original';
+ }
+ } elsif ($passwdconf{'captcha'} ne 'notused') {
+ $captcha = 'original';
+ }
+ }
}
- return ($captcha,$pubkey,$privkey);
+ return ($captcha,$pubkey,$privkey,$version);
}
sub create_captcha {
@@ -15521,13 +18619,17 @@ sub create_captcha {
if (-e $Apache::lonnet::perlvar{'lonCaptchaDir'}.'/'.$md5sum.'.png') {
$output = ' '."\n".
+ ''.
&mt('Type in the letters/numbers shown below').' '.
- ' '.
- ' '.
+ ' '.
+ ' '.
' ';
last;
}
}
+ if ($output eq '') {
+ &Apache::lonnet::logthis("Failed to create Captcha code after $tries attempts.");
+ }
return $output;
}
@@ -15564,38 +18666,63 @@ sub check_captcha {
}
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.',
- ' ').
- ' ';
+ my ($pubkey,$version) = @_;
+ if ($version >= 2) {
+ return '
'.
+ '
';
+ } else {
+ 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 the text is hard to read, [_1] will replace them.',
+ ' ').
+ ' ';
+ }
}
sub check_recaptcha {
- my ($privkey) = @_;
+ my ($privkey,$version) = @_;
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;
+ my $ip = &Apache::lonnet::get_requestor_ip();
+ if ($version >= 2) {
+ my $ua = LWP::UserAgent->new;
+ $ua->timeout(10);
+ my %info = (
+ secret => $privkey,
+ response => $env{'form.g-recaptcha-response'},
+ remoteip => $ip,
+ );
+ my $response = $ua->post('https://www.google.com/recaptcha/api/siteverify',\%info);
+ if ($response->is_success) {
+ my $data = JSON::DWIW->from_json($response->decoded_content);
+ if (ref($data) eq 'HASH') {
+ if ($data->{'success'}) {
+ $captcha_chk = 1;
+ }
+ }
+ }
+ } else {
+ my $captcha = Captcha::reCAPTCHA->new;
+ my $captcha_result =
+ $captcha->check_answer(
+ $privkey,
+ $ip,
+ $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 @fields = ('firstname','lastname','institution','web','location','officialemail','id');
my %titles = &Apache::lonlocal::texthash (
lastname => 'Last Name',
firstname => 'First Name',
@@ -15603,6 +18730,7 @@ sub emailusername_info {
location => "School's city, state/province, country",
web => "School's web address",
officialemail => 'E-mail address at institution (if different)',
+ id => 'Student/Employee ID',
);
return (\@fields,\%titles);
}
@@ -15629,6 +18757,45 @@ sub cleanup_html {
return $outgoing;
}
+# Checks for critical messages and returns a redirect url if one exists.
+# $interval indicates how often to check for messages.
+# $context is the calling context -- roles, grades, contents, menu or flip.
+sub critical_redirect {
+ my ($interval,$context) = @_;
+ unless (($env{'user.domain'} ne '') && ($env{'user.name'} ne '')) {
+ return ();
+ }
+ if ((time-$env{'user.criticalcheck.time'})>$interval) {
+ if (($env{'request.course.id'}) && (($context eq 'flip') || ($context eq 'contents'))) {
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $blocked = &blocking_status('alert',undef,$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})) {
+ return;
+ }
+ }
+ }
+ 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] ne 'no_such_host') && ($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);
#
@@ -15663,14 +18830,358 @@ sub des_decrypt {
} 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)) );
+ my $plaintext='';
+ my $cypherlength = length($cyphertext);
+ my $numchunks = int($cypherlength/32);
+ for (my $j=0; $j<$numchunks; $j++) {
+ my $start = $j*32;
+ my $cypherblock = substr($cyphertext,$start,32);
+ my $chunk =
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,0,16))));
+ $chunk .=
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,16,16))));
+ $chunk=substr($chunk,1,ord(substr($chunk,0,1)) );
+ $plaintext .= $chunk;
+ }
return $plaintext;
}
+sub get_requested_shorturls {
+ my ($cdom,$cnum,$navmap) = @_;
+ return unless (ref($navmap));
+ my ($numnew,$errors);
+ my @toshorten = &Apache::loncommon::get_env_multiple('form.addtiny');
+ if (@toshorten) {
+ my (%maps,%resources,%titles);
+ &Apache::loncourserespicker::enumerate_course_contents($navmap,\%maps,\%resources,\%titles,
+ 'shorturls',$cdom,$cnum);
+ if (keys(%resources)) {
+ my %tocreate;
+ foreach my $item (sort {$a <=> $b} (@toshorten)) {
+ my $symb = $resources{$item};
+ if ($symb) {
+ $tocreate{$cnum.'&'.$symb} = 1;
+ }
+ }
+ if (keys(%tocreate)) {
+ ($numnew,$errors) = &make_short_symbs($cdom,$cnum,
+ \%tocreate);
+ }
+ }
+ }
+ return ($numnew,$errors);
+}
+
+sub make_short_symbs {
+ my ($cdom,$cnum,$tocreateref,$lockuser) = @_;
+ my ($numnew,@errors);
+ if (ref($tocreateref) eq 'HASH') {
+ my %tocreate = %{$tocreateref};
+ if (keys(%tocreate)) {
+ my %coursetiny = &Apache::lonnet::dump('tiny',$cdom,$cnum);
+ my $su = Short::URL->new(no_vowels => 1);
+ my $init = '';
+ my (%newunique,%addcourse,%courseonly,%failed);
+ # get lock on tiny db
+ my $now = time;
+ if ($lockuser eq '') {
+ $lockuser = $env{'user.name'}.':'.$env{'user.domain'};
+ }
+ my $lockhash = {
+ "lock\0$now" => $lockuser,
+ };
+ my $tries = 0;
+ my $gotlock = &Apache::lonnet::newput_dom('tiny',$lockhash,$cdom);
+ my ($code,$error);
+ while (($gotlock ne 'ok') && ($tries<3)) {
+ $tries ++;
+ sleep 1;
+ $gotlock = &Apache::lonnet::newput_dom('tiny',$lockhash,$cdom);
+ }
+ if ($gotlock eq 'ok') {
+ $init = &shorten_symbs($cdom,$init,$su,\%coursetiny,\%tocreate,\%newunique,
+ \%addcourse,\%courseonly,\%failed);
+ if (keys(%failed)) {
+ my $numfailed = scalar(keys(%failed));
+ push(@errors,&mt('error: could not obtain unique six character URL for [quant,_1,resource]',$numfailed));
+ }
+ if (keys(%newunique)) {
+ my $putres = &Apache::lonnet::newput_dom('tiny',\%newunique,$cdom);
+ if ($putres eq 'ok') {
+ $numnew = scalar(keys(%newunique));
+ my $newputres = &Apache::lonnet::newput('tiny',\%addcourse,$cdom,$cnum);
+ unless ($newputres eq 'ok') {
+ push(@errors,&mt('error: could not store course look-up of short URLs'));
+ }
+ } else {
+ push(@errors,&mt('error: could not store unique six character URLs'));
+ }
+ }
+ my $dellockres = &Apache::lonnet::del_dom('tiny',["lock\0$now"],$cdom);
+ unless ($dellockres eq 'ok') {
+ push(@errors,&mt('error: could not release lockfile'));
+ }
+ } else {
+ push(@errors,&mt('error: could not obtain lockfile'));
+ }
+ if (keys(%courseonly)) {
+ my $result = &Apache::lonnet::newput('tiny',\%courseonly,$cdom,$cnum);
+ if ($result ne 'ok') {
+ push(@errors,&mt('error: could not update course look-up of short URLs'));
+ }
+ }
+ }
+ }
+ return ($numnew,\@errors);
+}
+
+sub shorten_symbs {
+ my ($cdom,$init,$su,$coursetiny,$tocreate,$newunique,$addcourse,$courseonly,$failed) = @_;
+ return unless ((ref($su)) && (ref($coursetiny) eq 'HASH') && (ref($tocreate) eq 'HASH') &&
+ (ref($newunique) eq 'HASH') && (ref($addcourse) eq 'HASH') &&
+ (ref($courseonly) eq 'HASH') && (ref($failed) eq 'HASH'));
+ my (%possibles,%collisions);
+ foreach my $key (keys(%{$tocreate})) {
+ my $num = String::CRC32::crc32($key);
+ my $tiny = $su->encode($num,$init);
+ if ($tiny) {
+ $possibles{$tiny} = $key;
+ }
+ }
+ if (!$init) {
+ $init = 1;
+ } else {
+ $init ++;
+ }
+ if (keys(%possibles)) {
+ my @posstiny = keys(%possibles);
+ my $configuname = &Apache::lonnet::get_domainconfiguser($cdom);
+ my %currtiny = &Apache::lonnet::get('tiny',\@posstiny,$cdom,$configuname);
+ if (keys(%currtiny)) {
+ foreach my $key (keys(%currtiny)) {
+ next if ($currtiny{$key} eq '');
+ if ($currtiny{$key} eq $possibles{$key}) {
+ my ($tcnum,$tsymb) = split(/\&/,$currtiny{$key});
+ unless (($coursetiny->{$tsymb} eq $key) || ($addcourse->{$tsymb} eq $key) || ($courseonly->{$tsymb} eq $key)) {
+ $courseonly->{$tsymb} = $key;
+ }
+ } else {
+ $collisions{$possibles{$key}} = 1;
+ }
+ delete($possibles{$key});
+ }
+ }
+ foreach my $key (keys(%possibles)) {
+ $newunique->{$key} = $possibles{$key};
+ my ($tcnum,$tsymb) = split(/\&/,$possibles{$key});
+ unless (($coursetiny->{$tsymb} eq $key) || ($addcourse->{$tsymb} eq $key) || ($courseonly->{$tsymb} eq $key)) {
+ $addcourse->{$tsymb} = $key;
+ }
+ }
+ }
+ if (keys(%collisions)) {
+ if ($init <5) {
+ if (!$init) {
+ $init = 1;
+ } else {
+ $init ++;
+ }
+ $init = &shorten_symbs($cdom,$init,$su,$coursetiny,\%collisions,
+ $newunique,$addcourse,$courseonly,$failed);
+ } else {
+ foreach my $key (keys(%collisions)) {
+ $failed->{$key} = 1;
+ $failed->{$key} = 1;
+ }
+ }
+ }
+ return $init;
+}
+
+sub is_nonframeable {
+ my ($url,$absolute,$hostname,$ip,$nocache) = @_;
+ my ($remprotocol,$remhost) = ($url =~ m{^(https?)\://(([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,})}i);
+ return if (($remprotocol eq '') || ($remhost eq ''));
+
+ $remprotocol = lc($remprotocol);
+ $remhost = lc($remhost);
+ my $remport = 80;
+ if ($remprotocol eq 'https') {
+ $remport = 443;
+ }
+ my ($result,$cached) = &Apache::lonnet::is_cached_new('noiframe',$remhost.':'.$remport);
+ if ($cached) {
+ unless ($nocache) {
+ if ($result) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ }
+ my $uselink;
+ my $request = new HTTP::Request('HEAD',$url);
+ my $ua = LWP::UserAgent->new;
+ $ua->timeout(5);
+ my $response=$ua->request($request);
+ if ($response->is_success()) {
+ my $secpolicy = lc($response->header('content-security-policy'));
+ my $xframeop = lc($response->header('x-frame-options'));
+ $secpolicy =~ s/^\s+|\s+$//g;
+ $xframeop =~ s/^\s+|\s+$//g;
+ if (($secpolicy ne '') || ($xframeop ne '')) {
+ my $remotehost = $remprotocol.'://'.$remhost;
+ my ($origin,$protocol,$port);
+ if ($ENV{'SERVER_PORT'} =~/^\d+$/) {
+ $port = $ENV{'SERVER_PORT'};
+ } else {
+ $port = 80;
+ }
+ if ($absolute eq '') {
+ $protocol = 'http:';
+ if ($port == 443) {
+ $protocol = 'https:';
+ }
+ $origin = $protocol.'//'.lc($hostname);
+ } else {
+ $origin = lc($absolute);
+ ($protocol,$hostname) = ($absolute =~ m{^(https?:)//([^/]+)$});
+ }
+ if (($secpolicy) && ($secpolicy =~ /\Qframe-ancestors\E([^;]*)(;|$)/)) {
+ my $framepolicy = $1;
+ $framepolicy =~ s/^\s+|\s+$//g;
+ my @policies = split(/\s+/,$framepolicy);
+ if (@policies) {
+ if (grep(/^\Q'none'\E$/,@policies)) {
+ $uselink = 1;
+ } else {
+ $uselink = 1;
+ if ((grep(/^\Q*\E$/,@policies)) || (grep(/^\Q$protocol\E$/,@policies)) ||
+ (($origin ne '') && (grep(/^\Q$origin\E$/,@policies))) ||
+ (($ip ne '') && (grep(/^\Q$ip\E$/,@policies)))) {
+ undef($uselink);
+ }
+ if ($uselink) {
+ if (grep(/^\Q'self'\E$/,@policies)) {
+ if (($origin ne '') && ($remotehost eq $origin)) {
+ undef($uselink);
+ }
+ }
+ }
+ if ($uselink) {
+ my @possok;
+ if ($ip ne '') {
+ push(@possok,$ip);
+ }
+ my $hoststr = '';
+ foreach my $part (reverse(split(/\./,$hostname))) {
+ if ($hoststr eq '') {
+ $hoststr = $part;
+ } else {
+ $hoststr = "$part.$hoststr";
+ }
+ if ($hoststr eq $hostname) {
+ push(@possok,$hostname);
+ } else {
+ push(@possok,"*.$hoststr");
+ }
+ }
+ if (@possok) {
+ foreach my $poss (@possok) {
+ last if (!$uselink);
+ foreach my $policy (@policies) {
+ if ($policy =~ m{^(\Q$protocol\E//|)\Q$poss\E(\Q:$port\E|)$}) {
+ undef($uselink);
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ } elsif ($xframeop ne '') {
+ $uselink = 1;
+ my @policies = split(/\s*,\s*/,$xframeop);
+ if (@policies) {
+ unless (grep(/^deny$/,@policies)) {
+ if ($origin ne '') {
+ if (grep(/^sameorigin$/,@policies)) {
+ if ($remotehost eq $origin) {
+ undef($uselink);
+ }
+ }
+ if ($uselink) {
+ foreach my $policy (@policies) {
+ if ($policy =~ /^allow-from\s*(.+)$/) {
+ my $allowfrom = $1;
+ if (($allowfrom ne '') && ($allowfrom eq $origin)) {
+ undef($uselink);
+ last;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if ($nocache) {
+ if ($cached) {
+ my $devalidate;
+ if ($uselink && !$result) {
+ $devalidate = 1;
+ } elsif (!$uselink && $result) {
+ $devalidate = 1;
+ }
+ if ($devalidate) {
+ &Apache::lonnet::devalidate_cache_new('noiframe',$remhost.':'.$remport);
+ }
+ }
+ } else {
+ if ($uselink) {
+ $result = 1;
+ } else {
+ $result = 0;
+ }
+ &Apache::lonnet::do_cache_new('noiframe',$remhost.':'.$remport,$result,3600);
+ }
+ return $uselink;
+}
+
+sub page_menu {
+ my ($menucolls,$menunum) = @_;
+ my %menu;
+ foreach my $item (split(/;/,$menucolls)) {
+ my ($num,$value) = split(/\%/,$item);
+ if ($num eq $menunum) {
+ my @entries = split(/\&/,$value);
+ foreach my $entry (@entries) {
+ my ($name,$fields) = split(/=/,$entry);
+ if (($name eq 'top') || ($name eq 'inline') || ($name eq 'foot') || ($name eq 'main')) {
+ $menu{$name} = $fields;
+ } else {
+ my @shown;
+ if ($fields =~ /,/) {
+ @shown = split(/,/,$fields);
+ } else {
+ @shown = ($fields);
+ }
+ if (@shown) {
+ foreach my $field (@shown) {
+ next if ($field eq '');
+ $menu{$field} = 1;
+ }
+ }
+ }
+ }
+ }
+ }
+ return %menu;
+}
+
1;
__END__;