--- loncom/interface/loncommon.pm 2011/12/20 22:43:34 1.1040
+++ loncom/interface/loncommon.pm 2015/02/25 19:22:24 1.1206
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# a pile of common routines
#
-# $Id: loncommon.pm,v 1.1040 2011/12/20 22:43:34 raeburn Exp $
+# $Id: loncommon.pm,v 1.1206 2015/02/25 19:22:24 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -67,9 +67,17 @@ use Apache::lonhtmlcommon();
use Apache::loncoursedata();
use Apache::lontexconvert();
use Apache::lonclonecourse();
+use Apache::lonuserutils();
+use Apache::lonuserstate();
+use Apache::courseclassifier();
use LONCAPA qw(:DEFAULT :match);
use DateTime::TimeZone;
use DateTime::Locale::Catalog;
+use Text::Aspell;
+use Authen::Captcha;
+use Captcha::reCAPTCHA;
+use Crypt::DES;
+use DynaLoader; # for Crypt::DES version
# ---------------------------------------------- Designs
use vars qw(%defaultdesign);
@@ -154,6 +162,9 @@ sub ssi_with_retries {
# ----------------------------------------------- Filetypes/Languages/Copyright
my %language;
my %supported_language;
+my %supported_codes;
+my %latex_language; # For choosing hyphenation in
'; + } $endbodytag=&Apache::lontexconvert::jsMath_process()."\n".$endbodytag; if ( exists( $env{'internal.head.redirect'} ) ) { if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) { @@ -4940,7 +5683,7 @@ sub standard_css { my $mono = 'monospace'; my $data_table_head = $sidebg; my $data_table_light = '#FAFAFA'; - my $data_table_dark = '#F0F0F0'; + my $data_table_dark = '#E0E0E0'; my $data_table_darker = '#CCCCCC'; my $data_table_highlight = '#FFFF00'; my $mail_new = '#FFBB77'; @@ -4980,7 +5723,6 @@ body { a:focus, a:focus img { color: red; - background: yellow; } form, .inline { @@ -4995,6 +5737,14 @@ form, .inline { vertical-align:middle; } +.LC_floatleft { + float: left; +} + +.LC_floatright { + float: right; +} + .LC_400Box { width:400px; } @@ -5033,10 +5783,12 @@ form, .inline { .LC_error { color: red; - font-size: larger; } -.LC_warning, +.LC_warning { + color: darkorange; +} + .LC_diff_removed { color: red; } @@ -5075,35 +5827,36 @@ div.LC_confirm_box .LC_success img { } .LC_discussion { - background: $tabbg; + background: $data_table_dark; border: 1px solid black; margin: 2px; } -.LC_disc_action_links_bar { - background: $tabbg; - border: none; - margin: 4px; -} - .LC_disc_action_left { + background: $sidebg; text-align: left; + padding: 4px; + margin: 2px; } .LC_disc_action_right { + background: $sidebg; text-align: right; + padding: 4px; + margin: 2px; } .LC_disc_new_item { background: white; border: 2px solid red; - margin: 2px; + margin: 4px; + padding: 4px; } .LC_disc_old_item { background: white; - border: 1px solid black; - margin: 2px; + margin: 4px; + padding: 4px; } table.LC_pastsubmission { @@ -5198,11 +5951,11 @@ td.LC_table_cell_checkbox { text-align: left; } -.LC_head_subbox { +.LC_head_subbox, .LC_actionbox { clear:both; background: #F8F8F8; /* $sidebg; */ border: 1px solid $sidebg; - margin: 0 0 10px 0; + margin: 0 0 10px 0; padding: 3px; text-align: left; } @@ -5225,7 +5978,7 @@ td.LC_table_cell_checkbox { vertical-align: middle; } -li.LC_menubuttons_inline_text img,a { +li.LC_menubuttons_inline_text img { cursor:pointer; text-decoration: none; } @@ -5335,7 +6088,8 @@ table.LC_nested tr.LC_empty_row td { padding: 8px; } -table.LC_data_table tr.LC_empty_row td { +table.LC_data_table tr.LC_empty_row td, +table.LC_data_table tr.LC_footer_row td { background-color: $sidebg; } @@ -5887,6 +6641,10 @@ span.LC_cusr_subheading { font-size: 85%; } +span.LC_math-error { + border: solid 1px red; min-width: 1px; +} + div.LC_docs_entry_move { border: 1px solid #BBBBBB; background: #DDDDDD; @@ -5897,7 +6655,6 @@ div.LC_docs_entry_move { table.LC_data_table tr > td.LC_docs_entry_commands, table.LC_data_table tr > td.LC_docs_entry_parameter { - background: #DDDDDD; font-size: x-small; } @@ -6027,6 +6784,7 @@ div.LC_edit_problem_footer { font-weight: normal; font-size: medium; margin: 2px; + background-color: $sidebg; } div.LC_edit_problem_header, @@ -6036,6 +6794,7 @@ div.LC_edit_problem_footer div, div.LC_edit_problem_editxml_header, div.LC_edit_problem_editxml_header div { margin-top: 5px; + z-index: 100; } div.LC_edit_problem_header_title { @@ -6043,6 +6802,7 @@ div.LC_edit_problem_header_title { font-size: larger; background: $tabbg; padding: 3px; + margin: 0 0 5px 0; } table.LC_edit_problem_header_title { @@ -6052,7 +6812,12 @@ table.LC_edit_problem_header_title { div.LC_edit_problem_discards { float: left; - padding-bottom: 5px; +} + +div.LC_edit_actionbar { + margin: -5px 0px 0px 0px !important; + background-color: $sidebg; + height: 31px; } div.LC_edit_problem_saves { @@ -6060,6 +6825,23 @@ div.LC_edit_problem_saves { padding-bottom: 5px; } +.LC_edit_opt { + padding-left: 1em; + white-space: nowrap; +} + +.LC_edit_problem_latexhelper{ + text-align: right; +} + +#LC_edit_problem_colorful div{ + margin-left: 40px; +} + +#LC_edit_problem_codemirror div{ + margin-left: 0px; +} + img.stift { border-width: 0; vertical-align: middle; @@ -6074,13 +6856,13 @@ div.LC_createcourse { } .LC_dccid { + float: right; margin: 0.2em 0 0 0; padding: 0; font-size: 90%; display:none; } -a:hover, ol.LC_primary_menu a:hover, ol#LC_MenuBreadcrumbs a:hover, ol#LC_PathBreadcrumbs a:hover, @@ -6172,9 +6954,8 @@ fieldset > legend { } ol.LC_primary_menu { - float: right; margin: 0; - background-color: $pgbg_or_bgcolor; + padding: 0; } ol#LC_PathBreadcrumbs { @@ -6182,14 +6963,91 @@ ol#LC_PathBreadcrumbs { } ol.LC_primary_menu li { - display: inline; - padding: 5px 5px 0 10px; + color: RGB(80, 80, 80); + vertical-align: middle; + text-align: left; + list-style: none; + position: relative; + float: left; + z-index: 100; /* will be displayed above codemirror and underneath the help-layer */ + line-height: 1.5em; +} + +ol.LC_primary_menu li a, +ol.LC_primary_menu li p { + display: block; + margin: 0; + padding: 0 5px 0 10px; + text-decoration: none; +} + +ol.LC_primary_menu li p span.LC_primary_menu_innertitle { + display: inline-block; + width: 95%; + text-align: left; +} + +ol.LC_primary_menu li p span.LC_primary_menu_innerarrow { + display: inline-block; + width: 5%; + float: right; + text-align: right; + font-size: 70%; +} + +ol.LC_primary_menu ul { + display: none; + width: 15em; + background-color: $data_table_light; + position: absolute; + top: 100%; +} + +ol.LC_primary_menu ul ul { + left: 100%; + top: 0; +} + +ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul { + display: block; + position: absolute; + margin: 0; + padding: 0; + z-index: 2; +} + +ol.LC_primary_menu li:hover li, ol.LC_primary_menu li.hover li { +/* First Submenu -> size should be smaller than the menu title of the whole menu */ + font-size: 90%; vertical-align: top; + float: none; + border-left: 1px solid black; + border-right: 1px solid black; +/* A dark bottom border to visualize different menu options; +overwritten in the create_submenu routine for the last border-bottom of the menu */ + border-bottom: 1px solid $data_table_dark; +} + +ol.LC_primary_menu li li p:hover { + color:$button_hover; + text-decoration:none; + background-color:$data_table_dark; +} + +ol.LC_primary_menu li li a:hover { + color:$button_hover; + background-color:$data_table_dark; +} + +/* Font-size equal to the size of the predecessors*/ +ol.LC_primary_menu li:hover li li { + font-size: 100%; } ol.LC_primary_menu li img { vertical-align: bottom; height: 1.1em; + margin: 0.2em 0 0 0; } ol.LC_primary_menu a { @@ -6227,7 +7085,7 @@ ol.LC_docs_parameters li.LC_docs_paramet } ul#LC_secondary_menu { - clear: both; + clear: right; color: $fontmenu; background: $tabbg; list-style: none; @@ -6235,15 +7093,52 @@ ul#LC_secondary_menu { margin: 0; width: 100%; text-align: left; + float: left; } ul#LC_secondary_menu li { font-weight: bold; line-height: 1.8em; + border-right: 1px solid black; + float: left; +} + +ul#LC_secondary_menu li.LC_hoverable:hover, ul#LC_secondary_menu li.hover { + background-color: $data_table_light; +} + +ul#LC_secondary_menu li a { padding: 0 0.8em; +} + +ul#LC_secondary_menu li ul { + display: none; +} + +ul#LC_secondary_menu li:hover ul, ul#LC_secondary_menu li.hover ul { + display: block; + position: absolute; + margin: 0; + padding: 0; + list-style:none; + float: none; + background-color: $data_table_light; + z-index: 2; + margin-left: -1px; +} + +ul#LC_secondary_menu li ul li { + font-size: 90%; + vertical-align: top; + border-left: 1px solid black; border-right: 1px solid black; - display: inline; - vertical-align: middle; + background-color: $data_table_light; + list-style:none; + float: none; +} + +ul#LC_secondary_menu li ul li:hover, ul#LC_secondary_menu li ul li.hover { + background-color: $data_table_dark; } ul.LC_TabContent { @@ -6314,6 +7209,12 @@ ul.LC_TabContent li.active a { background:#FFFFFF; outline: none; } + +ul.LC_TabContent li.goback { + float: left; + border-left: none; +} + #maincoursedoc { clear:both; } @@ -6563,6 +7464,10 @@ a#LC_content_toolbar_changefolder_toggle background-image:url(/res/adm/pages/open-all-folders.gif); } +a#LC_content_toolbar_edittoplevel { + background-image:url(/res/adm/pages/edittoplevel.gif); +} + ul#LC_toolbar li a:hover { background-position: bottom center; } @@ -6573,6 +7478,7 @@ ul#LC_toolbar { list-style:none; position:relative; background-color:white; + overflow: auto; } ul#LC_toolbar li { @@ -6582,6 +7488,7 @@ ul#LC_toolbar li { float: left; display:inline; vertical-align:middle; + white-space: nowrap; } @@ -6678,6 +7585,27 @@ ul.LC_funclist li { cursor:pointer; } +/* + styles used by TTH when "Default set of options to pass to tth/m + when converting TeX" in course settings has been set + + option passed: -t + +*/ + +td div.comp { margin-top: -0.6ex; margin-bottom: -1ex;} +td div.comb { margin-top: -0.6ex; margin-bottom: -.6ex;} +td div.hrcomp { line-height: 0.9; margin-top: -0.8ex; margin-bottom: -1ex;} +td div.norm {line-height:normal;} + +/* + option passed -y3 +*/ + +span.roman {font-family: serif; font-style: normal; font-weight: normal;} +span.overacc2 {position: relative; left: .8em; top: -1.2ex;} +span.overacc1 {position: relative; left: .6em; top: -1.2ex;} + END } @@ -6714,6 +7642,7 @@ sub headtag { my $function = $args->{'function'} || &get_users_function(); my $domain = $args->{'domain'} || &determinedomain(); my $bgcolor = $args->{'bgcolor'} || &designparm($function.'.pgbg',$domain); + my $httphost = $args->{'use_absolute'}; my $url = join(':',$env{'user.name'},$env{'user.domain'}, $Apache::lonnet::perlvar{'lonVersion'}, #time(), @@ -6724,7 +7653,12 @@ sub headtag { my $result = '
'. - &font_settings(); + &font_settings($args); + + my $inhibitprint; + if ($args->{'print_suppress'}) { + $inhibitprint = &print_suppression(); + } if (!$args->{'frameset'}) { $result .= &Apache::lonhtmlcommon::htmlareaheaders(); @@ -6735,10 +7669,11 @@ sub headtag { if (!$args->{'no_nav_bar'} && !$args->{'only_body'} && !$args->{'frameset'}) { - $result .= &help_menu_js(); + $result .= &help_menu_js($httphost); $result.=&modal_window(); $result.=&togglebox_script(); $result.=&wishlist_window(); + $result.=&LCprogressbarUpdate_script(); } else { if ($args->{'add_modal'}) { $result.=&modal_window(); @@ -6749,6 +7684,9 @@ sub headtag { if ($args->{'add_togglebox'}) { $result.=&togglebox_script(); } + if ($args->{'add_progressbar'}) { + $result.=&LCprogressbarUpdate_script(); + } } if (ref($args->{'redirect'})) { my ($time,$url,$inhibit_continue) = @{$args->{'redirect'}}; @@ -6766,8 +7704,18 @@ ADDMETA } if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); } $result .= '
'
- .''
+ .'{'frameset'}) {
+ $result .= ' /';
+ }
+ $result .= '>'
+ .$inhibitprint
.$head_extra;
+ if ($env{'browser.mobile'}) {
+ $result .= '
+
+';
+ }
return $result.'';
}
@@ -6777,21 +7725,103 @@ ADDMETA
Returns neccessary to set the proper encoding
-Inputs: none
+Inputs: optional reference to HASH -- $args passed to &headtag()
=cut
sub font_settings {
+ my ($args) = @_;
my $headerstring='';
- if (!$env{'browser.mathml'} && $env{'browser.unicode'}) {
- $headerstring.=
- '';
+ if ((!$env{'browser.mathml'} && $env{'browser.unicode'}) ||
+ ((ref($args) eq 'HASH') && ($args->{'browser.unicode'}))) {
+ $headerstring.=
+ '{'frameset'}) {
+ $headerstring.= ' /';
+ }
+ $headerstring .= '>'."\n";
}
return $headerstring;
}
=pod
+=item * &print_suppression()
+
+In course context returns css which causes the body to be blank when media="print",
+if printout generation is unavailable for the current resource.
+
+This could be because:
+
+(a) printstartdate is in the future
+
+(b) printenddate is in the past
+
+(c) there is an active exam block with "printout"
+functionality blocked
+
+Users with pav, pfo or evb privileges are exempt.
+
+Inputs: none
+
+=cut
+
+
+sub print_suppression {
+ my $noprint;
+ if ($env{'request.course.id'}) {
+ my $scope = $env{'request.course.id'};
+ if ((&Apache::lonnet::allowed('pav',$scope)) ||
+ (&Apache::lonnet::allowed('pfo',$scope))) {
+ return;
+ }
+ if ($env{'request.course.sec'} ne '') {
+ $scope .= "/$env{'request.course.sec'}";
+ if ((&Apache::lonnet::allowed('pav',$scope)) ||
+ (&Apache::lonnet::allowed('pfo',$scope))) {
+ return;
+ }
+ }
+ my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ my $blocked = &blocking_status('printout',$cnum,$cdom,undef,1);
+ if ($blocked) {
+ my $checkrole = "cm./$cdom/$cnum";
+ if ($env{'request.course.sec'} ne '') {
+ $checkrole .= "/$env{'request.course.sec'}";
+ }
+ unless ((&Apache::lonnet::allowed('evb',undef,undef,$checkrole)) &&
+ ($env{'request.role'} !~ m{^st\./$cdom/$cnum})) {
+ $noprint = 1;
+ }
+ }
+ unless ($noprint) {
+ my $symb = &Apache::lonnet::symbread();
+ if ($symb ne '') {
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (ref($navmap)) {
+ my $res = $navmap->getBySymb($symb);
+ if (ref($res)) {
+ if (!$res->resprintable()) {
+ $noprint = 1;
+ }
+ }
+ }
+ }
+ }
+ if ($noprint) {
+ return <<"ENDSTYLE";
+
+ENDSTYLE
+ }
+ }
+ return;
+}
+
+=pod
+
=item * &xml_begin()
Returns the needed doctype and
@@ -6801,6 +7831,7 @@ Inputs: none
=cut
sub xml_begin {
+ my ($is_frameset) = @_;
my $output='';
if ($env{'browser.mathml'}) {
@@ -6812,9 +7843,12 @@ sub xml_begin {
.''
.'';
+ } elsif ($is_frameset) {
+ $output=''."\n".
+ ''."\n";
} else {
- $output=''
- .'';
+ $output=''."\n".
+ ''."\n";
}
return $output;
}
@@ -6864,6 +7898,8 @@ $args - additional optional args support
current page
bread_crumbs -> Array containing breadcrumbs
bread_crumbs_component -> if exists show it as headline else show only the breadcrumbs
+ group -> includes the current group, if page is for a
+ specific group
=back
@@ -6876,10 +7912,10 @@ sub start_page {
#&Apache::lonnet::logthis("start_page ".join(':',caller(0)));
$env{'internal.start_page'}++;
- my $result;
+ my ($result,@advtools);
if (! exists($args->{'skip_phases'}{'head'}) ) {
- $result .= &xml_begin() . &headtag($title, $head_extra, $args);
+ $result .= &xml_begin($args->{'frameset'}) . &headtag($title, $head_extra, $args);
}
if (! exists($args->{'skip_phases'}{'body'}) ) {
@@ -6893,7 +7929,8 @@ sub start_page {
$args->{'function'}, $args->{'add_entries'},
$args->{'only_body'}, $args->{'domain'},
$args->{'force_register'}, $args->{'no_nav_bar'},
- $args->{'bgcolor'}, $args);
+ $args->{'bgcolor'}, $args,
+ \@advtools);
}
}
@@ -6922,6 +7959,10 @@ sub start_page {
&Apache::lonhtmlcommon::add_breadcrumb($crumb);
}
}
+ # if @advtools array contains items add then to the breadcrumbs
+ if (@advtools > 0) {
+ &Apache::lonmenu::advtools_crumbs(@advtools);
+ }
#if bread_crumbs_component exists show it as headline else show only the breadcrumbs
if(exists($args->{'bread_crumbs_component'})){
@@ -6950,7 +7991,9 @@ sub end_page {
} else {
$result .= &endbodytag($args);
}
- $result .= "\n";
+ unless ($args->{'notbody'}) {
+ $result .= "\n";
+ }
if ($args->{'js_ready'}) {
$result = &js_ready($result);
@@ -6965,7 +8008,7 @@ sub end_page {
sub wishlist_window {
return(<<'ENDWISHLIST');
-
+ENDLAUNCH
+}
+
+sub modal_adhoc_close {
+ return (< '.$msg.' '.
+ &mt("Unable to $action [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.",
+ ''.$filename.'',$filesize).' '.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+ $disk_quota,$current_disk_usage).
+ ' '.&mt('or').' '.&mt('or').' '.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).','','').' '.&mt('Or [_1]continue[_2] the testbank import without modifying the reference(s).',
+ '','').' '.&mt('Changes need to be made to the reference(s) used for one or more of the dependencies, if your HTML file is to work correctly:').' '.&mt('Updated [quant,_1,reference] in [_2].',
$count,''.
- $fname.'').' '.
&mt('Error: update failed for: [_1].',
''.
$container.'').' '.&mt('Rewrote [quant,_1,link] as [quant,_1,absolute link] in [_2].',
+ $count,''.
+ $fname.'').' '.
+ &mt('Error: could not update links in [_1].',
+ ''.
+ $container.'').' '.
&mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded if existing (smaller) file with same name (size = [_3] kilobytes) is replaced.',
- ''.$fname.'',$filesize,$currsize).' '.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
+ $disk_quota,$current_disk_usage).' '.
+ &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.',''.$fname.'',$filesize).' '.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage).' ';
$msg .= &mt('Unable to upload [_1]. A locked file by that name was found in [_2].',''.$fname.'',''.$port_path.$env{'form.currentpath'}.'');
- $msg .= ' ';
$msg .= &mt(' A file by that name: [_1] was found in [_2].',''.$fname.'',$port_path.$env{'form.currentpath'});
- $msg .= ' $lt{'camt'} '.$lt{'this'};
+ if ($info eq '') {
+ $output .= ' '.$lt{'youm'}.' '.
+ &mt('Files extracted successfully from archive.').
+ ' '.&mt('Not extracted.').' '.$warning.' '.
+ &mt('Course home server failed to retrieve:').' '.&mt('Not extracted.').' '.$warning.' '.
&end_data_table_row();
- }
+ }
}
} else {
$modifyform = $pathchgtable;
@@ -9247,6 +11013,9 @@ sub modify_html_form {
}
}
if ($modifyform) {
+ if ($actionurl eq '/adm/dependencies') {
+ $hiddenstate .= '';
+ }
return '
';
}
+sub nicescroll_javascript {
+ my ($id,$cursor,$needjsready,$framecheck,$location) = @_;
+ my %options;
+ if (ref($cursor) eq 'HASH') {
+ %options = %{$cursor};
+ }
+ unless ($options{'railalign'} =~ /^left|right$/) {
+ $options{'railalign'} = 'left';
+ }
+ unless ($options{'cursorcolor'} =~ /^\#\w+$/) {
+ my $function = &get_users_function();
+ $options{'cursorcolor'} = &designparm($function.'.sidebg',$env{'request.role.domain'});
+ unless ($options{'cursorcolor'} =~ /^\#\w+$/) {
+ $options{'cursorcolor'} = '#00F';
+ }
+ }
+ if ($options{'cursoropacity'} =~ /^[\d.]+$/) {
+ unless ($options{'cursoropacity'} >= 0.0 && $options{'cursoropacity'} <=1.0) {
+ $options{'cursoropacity'}='1.0';
+ }
+ } else {
+ $options{'cursoropacity'}='1.0';
+ }
+ if ($options{'cursorfixedheight'} eq 'none') {
+ delete($options{'cursorfixedheight'});
+ } else {
+ unless ($options{'cursorfixedheight'} =~ /^\d+$/) { $options{'cursorfixedheight'}='50'; }
+ }
+ unless ($options{'railoffset'} =~ /^{[\w\:\d\-,]+}$/) {
+ delete($options{'railoffset'});
+ }
+ my @niceoptions;
+ while (my($key,$value) = each(%options)) {
+ if ($value =~ /^\{.+\}$/) {
+ push(@niceoptions,$key.':'.$value);
+ } else {
+ push(@niceoptions,$key.':"'.$value.'"');
+ }
+ }
+ my $nicescroll_js = '
+$(document).ready(
+ function() {
+ $("#'.$id.'").niceScroll({'.join(',',@niceoptions).'});
+ }
+);
+';
+ if ($framecheck) {
+ $nicescroll_js .= '
+function expand_div(caller) {
+ if (top === self) {
+ document.getElementById("'.$id.'").style.width = "auto";
+ document.getElementById("'.$id.'").style.height = "auto";
+ } else {
+ try {
+ if (parent.frames) {
+ if (parent.frames.length > 1) {
+ var framesrc = parent.frames[1].location.href;
+ var currsrc = framesrc.replace(/\#.*$/,"");
+ if ((caller == "search") || (currsrc == "'.$location.'")) {
+ document.getElementById("'.$id.'").style.width = "auto";
+ document.getElementById("'.$id.'").style.height = "auto";
+ }
+ }
+ }
+ } catch (e) {
+ return;
+ }
+ }
+ return;
+}
+';
+ }
+ if ($needjsready) {
+ $nicescroll_js = '
+\n";
+ } else {
+ $nicescroll_js = &Apache::lonhtmlcommon::scripttag($nicescroll_js);
+ }
+ return $nicescroll_js;
+}
+
sub simple_error_page {
- my ($r,$title,$msg) = @_;
+ my ($r,$title,$msg,$args) = @_;
+ if (ref($args) eq 'HASH') {
+ if (!$args->{'no_auto_mt_msg'}) { $msg = &mt($msg); }
+ } else {
+ $msg = &mt($msg);
+ }
+
my $page =
&Apache::loncommon::start_page($title).
- &mt($msg).
+ '
+
'.&mt("$text{$check} with the following format(s) may only be used for verified users at [_1]:",$domdesc).' ';
+ $output = '
'."\n";
+ $output .= ''."\n".''."\n";
} elsif ($numpathchg) {
my %pathchange = ();
$output .= &modify_html_form('pathchange',$actionurl,$state,\%pathchange,$pathchange_output);
if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
$output .= '
'.
+ &mt($text{$check}.' with the following format(s) may [_1]only[_2] be used for verified users at [_3]:',
+ '','',$domdesc).
+ ' ';
foreach my $rule (@{$ruleorder}) {
if (ref($curr_rules) eq 'ARRAY') {
if (grep(/^\Q$rule\E$/,@{$curr_rules})) {
@@ -8375,7 +9786,14 @@ sub personal_data_fieldtitles {
sub sorted_inst_types {
my ($dom) = @_;
- my ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+ my ($usertypes,$order);
+ my %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+ if (ref($domdefaults{'inststatus'}) eq 'HASH') {
+ $usertypes = $domdefaults{'inststatus'}{'inststatustypes'};
+ $order = $domdefaults{'inststatus'}{'inststatusorder'};
+ } else {
+ ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom);
+ }
my $othertitle = &mt('All users');
if ($env{'request.course.id'}) {
$othertitle = &mt('Any users');
@@ -8623,6 +10041,8 @@ sub get_future_slots {
=pod
+=back
+
=head1 HTTP Helpers
=over 4
@@ -8761,21 +10181,36 @@ sub get_env_multiple {
sub ask_for_embedded_content {
my ($actionurl,$state,$allfiles,$codebase,$args)=@_;
- my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges);
- my $num = 0;
+ my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges,
+ %currsubfile,%unused,$rem);
+ my $counter = 0;
+ my $numnew = 0;
my $numremref = 0;
my $numinvalid = 0;
my $numpathchg = 0;
my $numexisting = 0;
- my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath);
- if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
+ my $numunused = 0;
+ my ($output,$upload_output,$toplevel,$url,$udom,$uname,$getpropath,$cdom,$cnum,
+ $fileloc,$filename,$delete_output,$modify_output,$title,$symb,$path,$navmap);
+ my $heading = &mt('Upload embedded files');
+ my $buttontext = &mt('Upload');
+
+ if ($env{'request.course.id'}) {
+ if ($actionurl eq '/adm/dependencies') {
+ $navmap = Apache::lonnavmaps::navmap->new();
+ }
+ $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'};
+ $cnum = $env{'course.'.$env{'request.course.id'}.'.num'};
+ }
+ if (($actionurl eq '/adm/portfolio') ||
+ ($actionurl eq '/adm/coursegrp_portfolio')) {
my $current_path='/';
if ($env{'form.currentpath'}) {
$current_path = $env{'form.currentpath'};
}
if ($actionurl eq '/adm/coursegrp_portfolio') {
- $udom = $env{'course.'.$env{'request.course.id'}.'.domain'};
- $uname = $env{'course.'.$env{'request.course.id'}.'.num'};
+ $udom = $cdom;
+ $uname = $cnum;
$url = '/userfiles/groups/'.$env{'form.group'}.'/portfolio';
} else {
$udom = $env{'user.domain'};
@@ -8795,23 +10230,74 @@ sub ask_for_embedded_content {
}
} elsif ($actionurl eq '/adm/coursedocs') {
if (ref($args) eq 'HASH') {
- $url = $args->{'docs_url'};
- $toplevel = $url;
+ $url = $args->{'docs_url'};
+ $toplevel = $url;
+ if ($args->{'context'} eq 'paste') {
+ ($cdom,$cnum) = ($url =~ m{^\Q/uploaded/\E($match_domain)/($match_courseid)/});
+ ($path) =
+ ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
+ $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+ $fileloc =~ s{^/}{};
+ }
+ }
+ } elsif ($actionurl eq '/adm/dependencies') {
+ if ($env{'request.course.id'} ne '') {
+ if (ref($args) eq 'HASH') {
+ $url = $args->{'docs_url'};
+ $title = $args->{'docs_title'};
+ $toplevel = $url;
+ unless ($toplevel =~ m{^/}) {
+ $toplevel = "/$url";
+ }
+ ($rem) = ($toplevel =~ m{^(.+/)[^/]+$});
+ if ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/portfolio/syllabus\E)}) {
+ $path = $1;
+ } else {
+ ($path) =
+ ($toplevel =~ m{^(\Q/uploaded/$cdom/$cnum/\E(?:docs|supplemental)/(?:default|\d+)/\d+)/});
+ }
+ if ($toplevel=~/^\/*(uploaded|editupload)/) {
+ $fileloc = $toplevel;
+ $fileloc=~ s/^\s*(\S+)\s*$/$1/;
+ my ($udom,$uname,$fname) =
+ ($fileloc=~ m{^/+(?:uploaded|editupload)/+($match_domain)/+($match_name)/+(.*)$});
+ $fileloc = propath($udom,$uname).'/userfiles/'.$fname;
+ } else {
+ $fileloc = &Apache::lonnet::filelocation('',$toplevel);
+ }
+ $fileloc =~ s{^/}{};
+ ($filename) = ($fileloc =~ m{.+/([^/]+)$});
+ $heading = &mt('Status of dependencies in [_1]',"$title ($filename)");
+ }
}
- }
- my $now = time();
- foreach my $embed_file (keys(%{$allfiles})) {
- my $absolutepath;
+ } elsif ($actionurl eq "/public/$cdom/$cnum/syllabus") {
+ $udom = $cdom;
+ $uname = $cnum;
+ $url = "/uploaded/$cdom/$cnum/portfolio/syllabus";
+ $toplevel = $url;
+ $path = $url;
+ $fileloc = &Apache::lonnet::filelocation('',$toplevel).'/';
+ $fileloc =~ s{^/}{};
+ }
+ foreach my $file (keys(%{$allfiles})) {
+ my $embed_file;
+ if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) {
+ $embed_file = $1;
+ } else {
+ $embed_file = $file;
+ }
+ my ($absolutepath,$cleaned_file);
if ($embed_file =~ m{^\w+://}) {
- $newfiles{$embed_file} = 1;
- $mapping{$embed_file} = $embed_file;
+ $cleaned_file = $embed_file;
+ $newfiles{$cleaned_file} = 1;
+ $mapping{$cleaned_file} = $embed_file;
} else {
+ $cleaned_file = &clean_path($embed_file);
if ($embed_file =~ m{^/}) {
$absolutepath = $embed_file;
- $embed_file =~ s{^(/+)}{};
}
- if ($embed_file =~ m{/}) {
- my ($path,$fname) = ($embed_file =~ m{^(.+)/([^/]*)$});
+ if ($cleaned_file =~ m{/}) {
+ my ($path,$fname) = ($cleaned_file =~ m{^(.+)/([^/]*)$});
$path = &check_for_traversal($path,$url,$toplevel);
my $item = $fname;
if ($path ne '') {
@@ -8828,32 +10314,60 @@ sub ask_for_embedded_content {
} else {
$dependencies{$embed_file} = 1;
if ($absolutepath) {
- $mapping{$embed_file} = $absolutepath;
+ $mapping{$cleaned_file} = $absolutepath;
} else {
- $mapping{$embed_file} = $embed_file;
+ $mapping{$cleaned_file} = $embed_file;
}
}
}
}
+ my $dirptr = 16384;
foreach my $path (keys(%subdependencies)) {
- my %currsubfile;
- if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
+ $currsubfile{$path} = {};
+ if (($actionurl eq '/adm/portfolio') ||
+ ($actionurl eq '/adm/coursegrp_portfolio')) {
my ($sublistref,$listerror) =
&Apache::lonnet::dirlist($url.$path,$udom,$uname,$getpropath);
if (ref($sublistref) eq 'ARRAY') {
foreach my $line (@{$sublistref}) {
my ($file_name,$rest) = split(/\&/,$line,2);
- $currsubfile{$file_name} = 1;
+ $currsubfile{$path}{$file_name} = 1;
}
}
} elsif (($actionurl eq '/adm/upload') || ($actionurl eq '/adm/testbank')) {
if (opendir(my $dir,$url.'/'.$path)) {
my @subdir_list = grep(!/^\./,readdir($dir));
- map {$currsubfile{$_} = 1;} @subdir_list;
+ map {$currsubfile{$path}{$_} = 1;} @subdir_list;
+ }
+ } elsif (($actionurl eq '/adm/dependencies') ||
+ (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
+ ($args->{'context'} eq 'paste')) ||
+ ($actionurl eq "/public/$cdom/$cnum/syllabus")) {
+ if ($env{'request.course.id'} ne '') {
+ my $dir;
+ if ($actionurl eq "/public/$cdom/$cnum/syllabus") {
+ $dir = $fileloc;
+ } else {
+ ($dir) = ($fileloc =~ m{^(.+/)[^/]+$});
+ }
+ if ($dir ne '') {
+ my ($sublistref,$listerror) =
+ &Apache::lonnet::dirlist($dir.$path,$cdom,$cnum,$getpropath,undef,'/');
+ if (ref($sublistref) eq 'ARRAY') {
+ foreach my $line (@{$sublistref}) {
+ my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef,$size,
+ undef,$mtime)=split(/\&/,$line,12);
+ unless (($testdir&$dirptr) ||
+ ($file_name =~ /^\.\.?$/)) {
+ $currsubfile{$path}{$file_name} = [$size,$mtime];
+ }
+ }
+ }
+ }
}
}
foreach my $file (keys(%{$subdependencies{$path}})) {
- if ($currsubfile{$file}) {
+ if (exists($currsubfile{$path}{$file})) {
my $item = $path.'/'.$file;
unless ($mapping{$item} eq $item) {
$pathchanges{$item} = 1;
@@ -8864,9 +10378,27 @@ sub ask_for_embedded_content {
$newfiles{$path.'/'.$file} = 1;
}
}
+ if ($actionurl eq '/adm/dependencies') {
+ foreach my $path (keys(%currsubfile)) {
+ if (ref($currsubfile{$path}) eq 'HASH') {
+ foreach my $file (keys(%{$currsubfile{$path}})) {
+ unless ($subdependencies{$path}{$file}) {
+ next if (($rem ne '') &&
+ (($env{"httpref.$rem"."$path/$file"} ne '') ||
+ (ref($navmap) &&
+ (($navmap->getResourceByUrl($rem."$path/$file") ne '') ||
+ (($file =~ /^(.*\.s?html?)\.bak$/i) &&
+ ($navmap->getResourceByUrl($rem."$path/$1")))))));
+ $unused{$path.'/'.$file} = 1;
+ }
+ }
+ }
+ }
+ }
}
my %currfile;
- if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) {
+ if (($actionurl eq '/adm/portfolio') ||
+ ($actionurl eq '/adm/coursegrp_portfolio')) {
my ($dirlistref,$listerror) =
&Apache::lonnet::dirlist($url,$udom,$uname,$getpropath);
if (ref($dirlistref) eq 'ARRAY') {
@@ -8880,9 +10412,30 @@ sub ask_for_embedded_content {
my @dir_list = grep(!/^\./,readdir($dir));
map {$currfile{$_} = 1;} @dir_list;
}
+ } elsif (($actionurl eq '/adm/dependencies') ||
+ (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
+ ($args->{'context'} eq 'paste')) ||
+ ($actionurl eq "/public/$cdom/$cnum/syllabus")) {
+ if ($env{'request.course.id'} ne '') {
+ my ($dir) = ($fileloc =~ m{^(.+/)[^/]+$});
+ if ($dir ne '') {
+ my ($dirlistref,$listerror) =
+ &Apache::lonnet::dirlist($dir,$cdom,$cnum,$getpropath,undef,'/');
+ if (ref($dirlistref) eq 'ARRAY') {
+ foreach my $line (@{$dirlistref}) {
+ my ($file_name,$dom,undef,$testdir,undef,undef,undef,undef,
+ $size,undef,$mtime)=split(/\&/,$line,12);
+ unless (($testdir&$dirptr) ||
+ ($file_name =~ /^\.\.?$/)) {
+ $currfile{$file_name} = [$size,$mtime];
+ }
+ }
+ }
+ }
+ }
}
foreach my $file (keys(%dependencies)) {
- if ($currfile{$file}) {
+ if (exists($currfile{$file})) {
unless ($mapping{$file} eq $file) {
$pathchanges{$file} = 1;
}
@@ -8892,41 +10445,137 @@ sub ask_for_embedded_content {
$newfiles{$file} = 1;
}
}
+ foreach my $file (keys(%currfile)) {
+ unless (($file eq $filename) ||
+ ($file eq $filename.'.bak') ||
+ ($dependencies{$file})) {
+ if ($actionurl eq '/adm/dependencies') {
+ unless ($toplevel =~ m{^\Q/uploaded/$cdom/$cnum/portfolio/syllabus\E}) {
+ next if (($rem ne '') &&
+ (($env{"httpref.$rem".$file} ne '') ||
+ (ref($navmap) &&
+ (($navmap->getResourceByUrl($rem.$file) ne '') ||
+ (($file =~ /^(.*\.s?html?)\.bak$/i) &&
+ ($navmap->getResourceByUrl($rem.$1)))))));
+ }
+ }
+ $unused{$file} = 1;
+ }
+ }
+ if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') &&
+ ($args->{'context'} eq 'paste')) {
+ $counter = scalar(keys(%existing));
+ $numpathchg = scalar(keys(%pathchanges));
+ return ($output,$counter,$numpathchg,\%existing);
+ } elsif (($actionurl eq "/public/$cdom/$cnum/syllabus") &&
+ (ref($args) eq 'HASH') && ($args->{'context'} eq 'rewrites')) {
+ $counter = scalar(keys(%existing));
+ $numpathchg = scalar(keys(%pathchanges));
+ return ($output,$counter,$numpathchg,\%existing,\%mapping);
+ }
foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) {
+ if ($actionurl eq '/adm/dependencies') {
+ next if ($embed_file =~ m{^\w+://});
+ }
$upload_output .= &start_data_table_row().
- '
'.$embed_file.'';
+ ' '.
+ ''.$embed_file.'';
unless ($mapping{$embed_file} eq $embed_file) {
- $upload_output .= '
'.&mt('changed from: [_1]',$mapping{$embed_file}).'';
+ $upload_output .= '
'.
+ &mt('changed from: [_1]',$mapping{$embed_file}).'';
}
- $upload_output .= '';
- if ($args->{'ignore_remote_references'}
- && $embed_file =~ m{^\w+://}) {
- $upload_output.=''.&mt("URL points to other server.").'';
+ $upload_output .= ' ';
+ if ($args->{'ignore_remote_references'} && $embed_file =~ m{^\w+://}) {
+ $upload_output.=''.
+ ''.
+ &mt("URL points to web address").'';
$numremref++;
} elsif ($args->{'error_on_invalid_names'}
&& $embed_file ne &Apache::lonnet::clean_filename($embed_file,{'keep_path' => 1,})) {
-
- $upload_output.=''.&mt('Invalid characters').'';
+ $upload_output.=' '.
+ &mt('Invalid characters').'';
$numinvalid++;
} else {
- $upload_output .= &embedded_file_element('upload_embedded',$num,
+ $upload_output .= ' '.
+ &embedded_file_element('upload_embedded',$counter,
$embed_file,\%mapping,
- $allfiles,$codebase);
- $num++;
+ $allfiles,$codebase,'upload');
+ $counter ++;
+ $numnew ++;
}
$upload_output .= ' '.&Apache::loncommon::end_data_table_row()."\n";
}
foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%existing)) {
- $upload_output .= &start_data_table_row().
- ''.$embed_file.' '.
- ''.&mt('Already exists').' '.
- &Apache::loncommon::end_data_table_row()."\n";
+ if ($actionurl eq '/adm/dependencies') {
+ my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$embed_file);
+ $modify_output .= &start_data_table_row().
+ ''.
+ ''.
+ ' '.$embed_file.' '.
+ ''.$size.' '.
+ ''.$mtime.' '.
+ ' '.
+ &end_data_table_row()."\n";
+ $counter ++;
+ } else {
+ $upload_output .= &start_data_table_row().
+ ' '.
+ ''.$embed_file.' '.
+ ''.&mt('Already exists').' '.
+ &Apache::loncommon::end_data_table_row()."\n";
+ }
+ }
+ my $delidx = $counter;
+ foreach my $oldfile (sort {lc($a) cmp lc($b)} keys(%unused)) {
+ my ($size,$mtime) = &get_dependency_details(\%currfile,\%currsubfile,$oldfile);
+ $delete_output .= &start_data_table_row().
+ ''.
+ ' '.$oldfile.' '.
+ ''.$size.' '.
+ ''.$mtime.' '.
+ ' '.
+ &end_data_table_row()."\n";
+ $numunused ++;
+ $delidx ++;
}
if ($upload_output) {
$upload_output = &start_data_table().
$upload_output.
&end_data_table()."\n";
}
+ if ($modify_output) {
+ $modify_output = &start_data_table().
+ &start_data_table_header_row().
+ ''.&mt('File').' '.
+ ''.&mt('Size (KB)').' '.
+ ''.&mt('Modified').' '.
+ ''.&mt('Upload replacement?').' '.
+ &end_data_table_header_row().
+ $modify_output.
+ &end_data_table()."\n";
+ }
+ if ($delete_output) {
+ $delete_output = &start_data_table().
+ &start_data_table_header_row().
+ ''.&mt('File').' '.
+ ''.&mt('Size (KB)').' '.
+ ''.&mt('Modified').' '.
+ ''.&mt('Delete?').' '.
+ &end_data_table_header_row().
+ $delete_output.
+ &end_data_table()."\n";
+ }
my $applies = 0;
if ($numremref) {
$applies ++;
@@ -8937,22 +10586,44 @@ sub ask_for_embedded_content {
if ($numexisting) {
$applies ++;
}
- if ($num) {
+ if ($counter || $numunused) {
$output = '
';
+ $output .= &mt('Invalid file extension ([_1]) - reserved for internal use.',$1)
+ .' '.&mt('Rename the file with a different extension and re-upload.').'
';
next;
} elsif (($fname =~ /\.(\w+)$/) &&
(!defined(&Apache::loncommon::fileembstyle($1)))) {
$output .= &mt('Unrecognized file extension ([_1]) - rename the file with a proper extension and re-upload.',$1).'
';
next;
} elsif ($fname=~/\.(\d+)\.(\w+)$/) {
- $output .= &mt('File name not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2).'
';
+ $output .= &mt('Filename not allowed - rename the file to remove the number immediately before the file extension([_1]) and re-upload.',$2).'
';
next;
}
-
$env{'form.embedded_item_'.$i.'.filename'}=$fname;
+ my $subdir = $path;
+ $subdir =~ s{/+$}{};
if ($context eq 'portfolio') {
my $result;
if ($state eq 'existingfile') {
$result=
&Apache::lonnet::userfileupload('embedded_item_'.$i,'existingfile',
- $dirpath.$env{'form.currentpath'}.$path);
+ $dirpath.$env{'form.currentpath'}.$subdir);
} else {
$result=
&Apache::lonnet::userfileupload('embedded_item_'.$i,'',
$dirpath.
- $env{'form.currentpath'}.$path);
+ $env{'form.currentpath'}.$subdir);
if ($result !~ m|^/uploaded/|) {
$output .= ''
.&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].'
@@ -9132,10 +10890,11 @@ sub upload_embedded {
$path.$fname.'').'
';
}
}
- } elsif ($context eq 'coursedoc') {
+ } elsif (($context eq 'coursedoc') || ($context eq 'syllabus')) {
+ my $extendedsubdir = $dirpath.'/'.$subdir;
+ $extendedsubdir =~ s{/+$}{};
my $result =
- &Apache::lonnet::userfileupload('embedded_item_'.$i,'coursedoc',
- $dirpath.'/'.$path);
+ &Apache::lonnet::userfileupload('embedded_item_'.$i,$context,$extendedsubdir);
if ($result !~ m|^/uploaded/|) {
$output .= ''
.&mt('An error occurred ([_1]) while trying to upload [_2] for embedded element [_3].'
@@ -9145,6 +10904,9 @@ sub upload_embedded {
} else {
$output .= &mt('Uploaded [_1]',''.
$path.$fname.'').'
';
+ if ($context eq 'syllabus') {
+ &Apache::lonnet::make_public_indefinitely($result);
+ }
}
} else {
# Save the file
@@ -9165,13 +10927,15 @@ sub upload_embedded {
if (!open($fh,'>'.$dest)) {
&Apache::lonnet::logthis('Failed to create '.$dest);
$output .= ''.
- &mt('An error occurred while trying to upload [_1] for embedded element [_2].',$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
+ &mt('An error occurred while trying to upload [_1] for embedded element [_2].',
+ $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
'
';
} else {
if (!print $fh $env{'form.embedded_item_'.$i}) {
&Apache::lonnet::logthis('Failed to write to '.$dest);
$output .= ''.
- &mt('An error occurred while writing the file [_1] for embedded element [_2].',$orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
+ &mt('An error occurred while writing the file [_1] for embedded element [_2].',
+ $orig_uploaded_filename,$env{'form.embedded_orig_'.$i}).
'
';
} else {
$output .= &mt('Uploaded [_1]',''.
@@ -9193,15 +10957,17 @@ sub upload_embedded {
}
$output .= &modify_html_form('upload_embedded',$actionurl,$hiddenstate,\%pathchange);
$returnflag = 'ok';
- if (keys(%pathchange) > 0) {
+ my $numpathchgs = scalar(keys(%pathchange));
+ if ($numpathchgs > 0) {
if ($context eq 'portfolio') {
$output .= ''.&mt('Changes in content of HTML file required').'
'."\n".
''."\n".
'
+$noextract
+END
+ return $output;
+}
+
+sub decompression_utility {
+ my ($program) = @_;
+ my @utilities = ('tar','gunzip','bunzip2','unzip');
+ my $location;
+ if (grep(/^\Q$program\E$/,@utilities)) {
+ foreach my $dir ('/bin/','/usr/bin/','/usr/local/bin/','/sbin/',
+ '/usr/sbin/') {
+ if (-x $dir.$program) {
+ $location = $dir.$program;
+ last;
+ }
+ }
+ }
+ return $location;
+}
+
+sub list_archive_contents {
+ my ($file,$pathsref) = @_;
+ my (@cmd,$output);
+ my $needsregexp;
+ if ($file =~ /\.zip$/) {
+ @cmd = (&decompression_utility('unzip'),"-l");
+ $needsregexp = 1;
+ } elsif (($file =~ m/\.tar\.gz$/) ||
+ ($file =~ /\.tgz$/)) {
+ @cmd = (&decompression_utility('tar'),"-ztf");
+ } elsif ($file =~ /\.tar\.bz2$/) {
+ @cmd = (&decompression_utility('tar'),"-jtf");
+ } elsif ($file =~ m|\.tar$|) {
+ @cmd = (&decompression_utility('tar'),"-tf");
+ }
+ if (@cmd) {
+ undef($!);
+ undef($@);
+ if (open(my $fh,"-|", @cmd, $file)) {
+ while (my $line = <$fh>) {
+ $output .= $line;
+ chomp($line);
+ my $item;
+ if ($needsregexp) {
+ ($item) = ($line =~ /^\s*\d+\s+[\d\-]+\s+[\d:]+\s*(.+)$/);
+ } else {
+ $item = $line;
+ }
+ if ($item ne '') {
+ unless (grep(/^\Q$item\E$/,@{$pathsref})) {
+ push(@{$pathsref},$item);
+ }
+ }
+ }
+ close($fh);
+ }
+ }
+ return $output;
+}
+
+sub decompress_uploaded_file {
+ my ($file,$dir) = @_;
+ &Apache::lonnet::appenv({'cgi.file' => $file});
+ &Apache::lonnet::appenv({'cgi.dir' => $dir});
+ my $result = &Apache::lonnet::ssi_body('/cgi-bin/decompress.pl');
+ my ($handle) = ($env{'user.environment'} =~m{/([^/]+)\.id$});
+ my $lonidsdir = $Apache::lonnet::perlvar{'lonIDsDir'};
+ &Apache::lonnet::transfer_profile_to_env($lonidsdir,$handle,1);
+ my $decompressed = $env{'cgi.decompressed'};
+ &Apache::lonnet::delenv('cgi.file');
+ &Apache::lonnet::delenv('cgi.dir');
+ &Apache::lonnet::delenv('cgi.decompressed');
+ return ($decompressed,$result);
+}
+
+sub process_decompression {
+ my ($docudom,$docuname,$file,$destination,$dir_root,$hiddenelem) = @_;
+ my ($dir,$error,$warning,$output);
+ if ($file !~ /\.(zip|tar|bz2|gz|tar.gz|tar.bz2|tgz)$/i) {
+ $error = &mt('Filename not a supported archive file type.').
+ '
'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',
- $disk_quota,$current_disk_usage);
+ ''.$fname.'',$filesize,$currsize).''.
+ '
'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage);
+ my $msg = '
';
+ $msg .= '';
$msg .= &mt('You will be able to rename or delete existing [_1] after a grade has been assigned.',''.$fname.'');
return ('file_locked',$msg);
} else {
- my $msg = '';
+ my $msg = ''.$info.'
'.&mt('Filename should end with one of: [_1].',
+ '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz');
+ } else {
+ my $docuhome = &Apache::lonnet::homeserver($docuname,$docudom);
+ if ($docuhome eq 'no_host') {
+ $error = &mt('Could not determine home server for course.');
+ } else {
+ my @ids=&Apache::lonnet::current_machine_ids();
+ my $currdir = "$dir_root/$destination";
+ if (grep(/^\Q$docuhome\E$/,@ids)) {
+ $dir = &LONCAPA::propath($docudom,$docuname).
+ "$dir_root/$destination";
+ } else {
+ $dir = $Apache::lonnet::perlvar{'lonDocRoot'}.
+ "$dir_root/$docudom/$docuname/$destination";
+ unless (&Apache::lonnet::repcopy_userfile("$dir/$file") eq 'ok') {
+ $error = &mt('Archive file not found.');
+ }
+ }
+ my (@to_overwrite,@to_skip);
+ if ($env{'form.archive_overwrite_total'} > 0) {
+ my $total = $env{'form.archive_overwrite_total'};
+ for (my $i=0; $i<$total; $i++) {
+ if ($env{'form.archive_overwrite_'.$i} == 1) {
+ push(@to_overwrite,$env{'form.archive_overwrite_name_'.$i});
+ } elsif ($env{'form.archive_overwrite_'.$i} == 0) {
+ push(@to_skip,$env{'form.archive_overwrite_name_'.$i});
+ }
+ }
+ }
+ my $numskip = scalar(@to_skip);
+ if (($numskip > 0) &&
+ ($numskip == $env{'form.archive_itemcount'})) {
+ $warning = &mt('All items in the archive file already exist, and no overwriting of existing files has been requested.');
+ } elsif ($dir eq '') {
+ $error = &mt('Directory containing archive file unavailable.');
+ } elsif (!$error) {
+ my ($decompressed,$display);
+ if ($numskip > 0) {
+ my $tempdir = time.'_'.$$.int(rand(10000));
+ mkdir("$dir/$tempdir",0755);
+ system("mv $dir/$file $dir/$tempdir/$file");
+ ($decompressed,$display) =
+ &decompress_uploaded_file($file,"$dir/$tempdir");
+ foreach my $item (@to_skip) {
+ if (($item ne '') && ($item !~ /\.\./)) {
+ if (-f "$dir/$tempdir/$item") {
+ unlink("$dir/$tempdir/$item");
+ } elsif (-d "$dir/$tempdir/$item") {
+ system("rm -rf $dir/$tempdir/$item");
+ }
+ }
+ }
+ system("mv $dir/$tempdir/* $dir");
+ rmdir("$dir/$tempdir");
+ } else {
+ ($decompressed,$display) =
+ &decompress_uploaded_file($file,$dir);
+ }
+ if ($decompressed eq 'ok') {
+ $output = '
'.
+ $error.''.$count.' '."\n";
+ my $offset = 0;
+ foreach my $action ('display','dependency','discard') {
+ $offset ++;
+ if ($action ne 'display') {
+ $offset ++;
+ }
+ $output .= ''.
+ ' ';
+ }
+ $output .= '&').'" />'.(' ' x 2);
+ for (my $i=0; $i<$depth; $i++) {
+ $output .= ('' x2)."\n";
+ }
+ if ($is_dir) {
+ $output .= ' '."\n".
+ ''."\n";
+ } else {
+ $output .= ''."\n";
+ }
+ $output .= ' '.$name.' '."\n".
+ &end_data_table_row();
+ return $output;
+}
+
+sub archive_options_form {
+ my ($form,$display,$count,$hiddenelem) = @_;
+ my %lt = &Apache::lonlocal::texthash(
+ perm => 'Permanently remove archive file?',
+ hows => 'How should each extracted item be incorporated in the course?',
+ cont => 'Content actions for all',
+ addf => 'Add as folder/file',
+ incd => 'Include as dependency for a displayed file',
+ disc => 'Discard',
+ no => 'No',
+ yes => 'Yes',
+ save => 'Save',
+ );
+ my $output = <<"END";
+';
+}
+
+sub archive_javascript {
+ my ($startcount,$numitems,$titles,$children) = @_;
+ return unless ((ref($titles) eq 'HASH') && (ref($children) eq 'HASH'));
+ my $maintitle = $env{'form.comment'};
+ my $scripttag = <
';
+ }
+ }
+ for (my $i=1; $i<=$numitems; $i++) {
+ next unless ($env{'form.archive_'.$i} eq 'dependency');
+ my $path = $env{'form.archive_content_'.$i};
+ if ($path =~ /^\Q$pathtocheck\E/) {
+ my ($title) = ($path =~ m{/([^/]+)$});
+ $referrer{$i} = $env{'form.archive_dependent_on_'.$i};
+ if ($env{'form.archive_'.$referrer{$i}} eq 'display') {
+ if (ref($dirorder{$i}) eq 'ARRAY') {
+ my ($itemidx,$fullpath,$relpath);
+ if (ref($dirorder{$referrer{$i}}) eq 'ARRAY') {
+ my $container = $dirorder{$referrer{$i}}->[-1];
+ for (my $j=0; $j<@{$dirorder{$i}}; $j++) {
+ if ($dirorder{$i}->[$j] eq $container) {
+ $itemidx = $j;
+ }
+ }
+ }
+ if ($itemidx eq '') {
+ $itemidx = 0;
+ }
+ if (grep(/^\Q$referrer{$i}\E$/,@archdirs)) {
+ if ($mapinner{$referrer{$i}}) {
+ $fullpath = "$prefix$dir/$docstype/$mapinner{$referrer{$i}}";
+ for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+ if (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+ unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+ $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ if (!-e $fullpath) {
+ mkdir($fullpath,0755);
+ }
+ }
+ } else {
+ last;
+ }
+ }
+ }
+ } elsif ($newdest{$referrer{$i}}) {
+ $fullpath = $newdest{$referrer{$i}};
+ for (my $j=$itemidx; $j<@{$dirorder{$i}}; $j++) {
+ if ($env{'form.archive_'.$dirorder{$i}->[$j]} eq 'discard') {
+ $orphaned{$i} = $env{'form.archive_'.$dirorder{$i}->[$j]};
+ last;
+ } elsif (grep(/^\Q$dirorder{$i}->[$j]\E$/,@archdirs)) {
+ unless (defined($newseqid{$dirorder{$i}->[$j]})) {
+ $fullpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ $relpath .= '/'.$titles{$dirorder{$i}->[$j]};
+ if (!-e $fullpath) {
+ mkdir($fullpath,0755);
+ }
+ }
+ } else {
+ last;
+ }
+ }
+ }
+ if ($fullpath ne '') {
+ if (-e "$prefix$path") {
+ system("mv $prefix$path $fullpath/$title");
+ }
+ if (-e "$fullpath/$title") {
+ my $showpath;
+ if ($relpath ne '') {
+ $showpath = "$relpath/$title";
+ } else {
+ $showpath = "/$title";
+ }
+ $result .= '
';
+ }
+ } else {
+ $warning .= &mt('Item extracted from archive: [_1] has unexpected path.',$path).'
';
+ }
+ }
+ if (keys(%todelete)) {
+ foreach my $key (keys(%todelete)) {
+ unlink($key);
+ }
+ }
+ if (keys(%todeletedir)) {
+ foreach my $key (keys(%todeletedir)) {
+ rmdir($key);
+ }
+ }
+ foreach my $dir (sort(keys(%is_dir))) {
+ if (($pathtocheck ne '') && ($dir ne '')) {
+ &cleanup_empty_dirs($prefix."$pathtocheck/$dir");
+ }
+ }
+ if ($result ne '') {
+ $output .= ''."\n".
+ $result."\n".
+ '
';
+ }
+ unless ($ishome) {
+ my $replicationfail;
+ foreach my $item (keys(%prompttofetch)) {
+ my $fetchresult= &Apache::lonnet::reply('fetchuserfile:'.$item,$docuhome);
+ unless ($fetchresult eq 'ok') {
+ $replicationfail .= ''.
+ $replicationfail.
+ '
';
+ }
+ }
+ } else {
+ $warning = &mt('No items found in archive.');
+ }
+ if ($error) {
+ $output .= '
'.
+ $error.'';
+ $text .= '
';
for (my $j=0; $j<$numchildren; $j++) {
$name = $cats->[$depth]{$parent}[$j];
$item = &escape($name).':'.&escape($parent).':'.$depth;
@@ -10971,6 +14061,12 @@ sub assign_category_rows {
return $text;
}
+=pod
+
+=back
+
+=cut
+
############################################################
############################################################
@@ -10987,7 +14083,7 @@ sub commit_customrole {
}
sub commit_standardrole {
- my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context) = @_;
+ my ($udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,$credits) = @_;
my ($output,$logmsg,$linefeed);
if ($context eq 'auto') {
$linefeed = "\n";
@@ -10996,7 +14092,7 @@ sub commit_standardrole {
}
if ($three eq 'st') {
my $result = &commit_studentrole(\$logmsg,$udom,$uname,$url,$three,$start,$end,
- $one,$two,$sec,$context);
+ $one,$two,$sec,$context,$credits);
if (($result =~ /^error/) || ($result eq 'not_in_class') ||
($result eq 'unknown_course') || ($result eq 'refused')) {
$output = $logmsg.' '.&mt('Error: ').$result."\n";
@@ -11027,7 +14123,8 @@ sub commit_standardrole {
}
sub commit_studentrole {
- my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context) = @_;
+ my ($logmsg,$udom,$uname,$url,$three,$start,$end,$one,$two,$sec,$context,
+ $credits) = @_;
my ($result,$linefeed,$oldsecurl,$newsecurl);
if ($context eq 'auto') {
$linefeed = "\n";
@@ -11074,7 +14171,11 @@ sub commit_studentrole {
}
}
if (($expire_role_result eq 'ok') || ($secchange == 0)) {
- $modify_section_result = &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$sec,$end,$start,'','',$cid,'',$context);
+ $modify_section_result =
+ &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,
+ undef,undef,undef,$sec,
+ $end,$start,'','',$cid,
+ '',$context,$credits);
if ($modify_section_result =~ /^ok/) {
if ($secchange == 1) {
if ($sec eq '') {
@@ -11096,7 +14197,7 @@ sub commit_studentrole {
}
}
} else {
- if ($secchange) {
+ if ($secchange) {
$$logmsg .= &mt('Error when attempting section change for [_1] from old section "[_2]" to new section: "[_3]" in course [_4] -error:',$uname,$oldsec,$sec,$cid).' '.$modify_section_result.$linefeed;
} else {
$$logmsg .= &mt('Error when attempting to modify role for [_1] for section: "[_2]" in course [_3] -error:',$uname,$sec,$cid).' '.$modify_section_result.$linefeed;
@@ -11105,7 +14206,7 @@ sub commit_studentrole {
$result = $modify_section_result;
} elsif ($secchange == 1) {
if ($oldsec eq '') {
- $$logmsg .= &mt('Error when attempting to expire existing role without a section for [_1] in course [_3] -error: ',$uname,$cid).' '.$expire_role_result.$linefeed;
+ $$logmsg .= &mt('Error when attempting to expire existing role without a section for [_1] in course [_2] -error: ',$uname,$cid).' '.$expire_role_result.$linefeed;
} else {
$$logmsg .= &mt('Error when attempting to expire existing role for [_1] in section [_2] in course [_3] -error: ',$uname,$oldsec,$cid).' '.$expire_role_result.$linefeed;
}
@@ -11131,6 +14232,26 @@ sub commit_studentrole {
return $result;
}
+sub show_role_extent {
+ my ($scope,$context,$role) = @_;
+ $scope =~ s{^/}{};
+ my @courseroles = &Apache::lonuserutils::roles_by_context('course',1);
+ push(@courseroles,'co');
+ my @authorroles = &Apache::lonuserutils::roles_by_context('author');
+ if (($context eq 'course') || (grep(/^\Q$role\E/,@courseroles))) {
+ $scope =~ s{/}{_};
+ return ''.$env{'course.'.$scope.'.description'}.'';
+ } elsif (($context eq 'author') || (grep(/^\Q$role\E/,@authorroles))) {
+ my ($audom,$auname) = split(/\//,$scope);
+ return &mt('[_1] Author Space',''.
+ &Apache::loncommon::plainname($auname,$audom).'');
+ } else {
+ $scope =~ s{/$}{};
+ return &mt('Domain: [_1]',''.
+ &Apache::lonnet::domain($scope,'description').'');
+ }
+}
+
############################################################
############################################################
@@ -11198,7 +14319,7 @@ sub check_clone {
}
sub construct_course {
- my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category) = @_;
+ my ($args,$logmsg,$courseid,$crsudom,$crsunum,$udom,$uname,$context,$cnum,$category,$coderef) = @_;
my $outcome;
my $linefeed = '
'."\n";
if ($context eq 'auto') {
@@ -11294,8 +14415,13 @@ sub construct_course {
'pch.users.denied',
'plc.users.denied',
'hidefromcat',
- 'categories'],
+ 'checkforpriv',
+ 'categories',
+ 'internal.uniquecode'],
$$crsudom,$$crsunum);
+ if ($args->{'textbook'}) {
+ $cenv{'internal.textbook'} = $args->{'textbook'};
+ }
}
#
@@ -11323,6 +14449,9 @@ sub construct_course {
} else {
$cenv{'internal.courseowner'} = $args->{'curruser'};
}
+ if ($args->{'defaultcredits'}) {
+ $cenv{'internal.defaultcredits'} = $args->{'defaultcredits'};
+ }
my @badclasses = (); # Used to accumulate sections/crosslistings that did not pass classlist access check for course owner.
if ($args->{'crssections'}) {
$cenv{'internal.sectionnums'} = '';
@@ -11347,6 +14476,11 @@ sub construct_course {
# do not hide course coordinator from staff listing,
# even if privileged
$cenv{'nothideprivileged'}=$args->{'ccuname'}.':'.$args->{'ccdomain'};
+# add course coordinator's domain to domains to check for privileged users
+# if different to course domain
+ if ($$crsudom ne $args->{'ccdomain'}) {
+ $cenv{'checkforpriv'} = $args->{'ccdomain'};
+ }
# add crosslistings
if ($args->{'crsxlist'}) {
$cenv{'internal.crosslistings'}='';
@@ -11471,6 +14605,25 @@ sub construct_course {
}
}
+#
+# generate and store uniquecode (available to course requester), if course should have one.
+#
+ if ($args->{'uniquecode'}) {
+ my ($code,$error) = &make_unique_code($$crsudom,$$crsunum);
+ if ($code) {
+ $cenv{'internal.uniquecode'} = $code;
+ my %crsinfo =
+ &Apache::lonnet::courseiddump($$crsudom,'.',1,'.','.',$$crsunum,undef,undef,'.');
+ if (ref($crsinfo{$$crsudom.'_'.$$crsunum}) eq 'HASH') {
+ $crsinfo{$$crsudom.'_'.$$crsunum}{'uniquecode'} = $code;
+ my $putres = &Apache::lonnet::courseidput($$crsudom,\%crsinfo,$crsuhome,'notime');
+ }
+ if (ref($coderef)) {
+ $$coderef = $code;
+ }
+ }
+ }
+
if ($args->{'disresdis'}) {
$cenv{'pch.roles.denied'}='st';
}
@@ -11539,6 +14692,60 @@ sub construct_course {
return (1,$outcome);
}
+sub make_unique_code {
+ my ($cdom,$cnum) = @_;
+ # get lock on uniquecodes db
+ my $lockhash = {
+ $cnum."\0".'uniquecodes' => $env{'user.name'}.
+ ':'.$env{'user.domain'},
+ };
+ my $tries = 0;
+ my $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+ my ($code,$error);
+
+ while (($gotlock ne 'ok') && ($tries<3)) {
+ $tries ++;
+ sleep 1;
+ $gotlock = &Apache::lonnet::newput_dom('uniquecodes',$lockhash,$cdom);
+ }
+ if ($gotlock eq 'ok') {
+ my %currcodes = &Apache::lonnet::dump_dom('uniquecodes',$cdom);
+ my $gotcode;
+ my $attempts = 0;
+ while ((!$gotcode) && ($attempts < 100)) {
+ $code = &generate_code();
+ if (!exists($currcodes{$code})) {
+ $gotcode = 1;
+ unless (&Apache::lonnet::newput_dom('uniquecodes',{ $code => $cnum },$cdom) eq 'ok') {
+ $error = 'nostore';
+ }
+ }
+ $attempts ++;
+ }
+ my @del_lock = ($cnum."\0".'uniquecodes');
+ my $dellockoutcome = &Apache::lonnet::del_dom('uniquecodes',\@del_lock,$cdom);
+ } else {
+ $error = 'nolock';
+ }
+ return ($code,$error);
+}
+
+sub generate_code {
+ my $code;
+ my @letts = qw(B C D G H J K M N P Q R S T V W X Z);
+ for (my $i=0; $i<6; $i++) {
+ my $lettnum = int (rand 2);
+ my $item = '';
+ if ($lettnum) {
+ $item = $letts[int( rand(18) )];
+ } else {
+ $item = 1+int( rand(8) );
+ }
+ $code .= $item;
+ }
+ return $code;
+}
+
############################################################
############################################################
@@ -11566,11 +14773,12 @@ sub group_term {
}
sub course_types {
- my @types = ('official','unofficial','community');
+ my @types = ('official','unofficial','community','textbook');
my %typename = (
official => 'Official course',
unofficial => 'Unofficial course',
community => 'Community',
+ textbook => 'Textbook course',
);
return (\@types,\%typename);
}
@@ -11631,7 +14839,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 {
@@ -11659,7 +14867,7 @@ sub init_user_environment {
# See if old ID present, if so, remove
- my ($filename,$cookie,$userroles);
+ my ($filename,$cookie,$userroles,$firstaccenv,$timerintenv);
my $now=time;
if ($public) {
@@ -11689,6 +14897,17 @@ sub init_user_environment {
}
}
closedir(DIR);
+# If there is a undeleted lockfile for the user's paste buffer remove it.
+ my $namespace = 'nohist_courseeditor';
+ my $lockingkey = 'paste'."\0".'locked_num';
+ my %lockhash = &Apache::lonnet::get($namespace,[$lockingkey],
+ $domain,$username);
+ if (exists($lockhash{$lockingkey})) {
+ my $delresult = &Apache::lonnet::del($namespace,[$lockingkey],$domain,$username);
+ unless ($delresult eq 'ok') {
+ &Apache::lonnet::logthis("Failed to delete paste buffer locking key in $namespace for ".$username.":".$domain." Result was: $delresult");
+ }
+ }
}
# Give them a new cookie
my $id = ($args->{'robot'} ? 'robot'.$args->{'robot'}
@@ -11697,12 +14916,13 @@ sub init_user_environment {
# Initialize roles
- $userroles=&Apache::lonnet::rolesinit($domain,$username,$authhost);
+ ($userroles,$firstaccenv,$timerintenv) =
+ &Apache::lonnet::rolesinit($domain,$username,$authhost);
}
# ------------------------------------ Check browser type and MathML capability
- my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,
- $clientunicode,$clientos) = &decode_user_agent($r);
+ my ($httpbrowser,$clientbrowser,$clientversion,$clientmathml,$clientunicode,
+ $clientos,$clientmobile,$clientinfo,$clientosversion) = &decode_user_agent($r);
# ------------------------------------------------------------- Get environment
@@ -11733,6 +14953,9 @@ sub init_user_environment {
"browser.mathml" => $clientmathml,
"browser.unicode" => $clientunicode,
"browser.os" => $clientos,
+ "browser.mobile" => $clientmobile,
+ "browser.info" => $clientinfo,
+ "browser.osversion" => $clientosversion,
"server.domain" => $Apache::lonnet::perlvar{'lonDefDomain'},
"request.course.fn" => '',
"request.course.uri" => '',
@@ -11752,32 +14975,58 @@ sub init_user_environment {
$env{'browser.interface'}=$form->{'interface'};
}
+ if ($form->{'iptoken'}) {
+ my $lonhost = $r->dir_config('lonHostID');
+ $initial_env{"user.noloadbalance"} = $lonhost;
+ $env{'user.noloadbalance'} = $lonhost;
+ }
+
my %is_adv = ( is_adv => $env{'user.adv'} );
my %domdef;
unless ($domain eq 'public') {
%domdef = &Apache::lonnet::get_domain_defaults($domain);
}
- foreach my $tool ('aboutme','blog','portfolio') {
+ foreach my $tool ('aboutme','blog','webdav','portfolio') {
$userenv{'availabletools.'.$tool} =
&Apache::lonnet::usertools_access($username,$domain,$tool,'reload',
undef,\%userenv,\%domdef,\%is_adv);
}
- foreach my $crstype ('official','unofficial','community') {
+ foreach my $crstype ('official','unofficial','community','textbook') {
$userenv{'canrequest.'.$crstype} =
&Apache::lonnet::usertools_access($username,$domain,$crstype,
'reload','requestcourses',
\%userenv,\%domdef,\%is_adv);
}
+ $userenv{'canrequest.author'} =
+ &Apache::lonnet::usertools_access($username,$domain,'requestauthor',
+ 'reload','requestauthor',
+ \%userenv,\%domdef,\%is_adv);
+ my %reqauthor = &Apache::lonnet::get('requestauthor',['author_status','author'],
+ $domain,$username);
+ my $reqstatus = $reqauthor{'author_status'};
+ if ($reqstatus eq 'approval' || $reqstatus eq 'approved') {
+ if (ref($reqauthor{'author'}) eq 'HASH') {
+ $userenv{'requestauthorqueued'} = $reqstatus.':'.
+ $reqauthor{'author'}{'timestamp'};
+ }
+ }
+
$env{'user.environment'} = "$lonids/$cookie.id";
-
+
if (tie(my %disk_env,'GDBM_File',"$lonids/$cookie.id",
&GDBM_WRCREAT(),0640)) {
&_add_to_env(\%disk_env,\%initial_env);
&_add_to_env(\%disk_env,\%userenv,'environment.');
&_add_to_env(\%disk_env,$userroles);
+ if (ref($firstaccenv) eq 'HASH') {
+ &_add_to_env(\%disk_env,$firstaccenv);
+ }
+ if (ref($timerintenv) eq 'HASH') {
+ &_add_to_env(\%disk_env,$timerintenv);
+ }
if (ref($args->{'extra_env'})) {
&_add_to_env(\%disk_env,$args->{'extra_env'});
}
@@ -11813,7 +15062,9 @@ sub get_symb {
my $symb=($env{'form.symb'} ne '' ? $env{'form.symb'} : (&Apache::lonnet::symbread($url)));
if ($symb eq '') {
if (!$silent) {
- $request->print("Unable to handle ambiguous references:$url:.");
+ if (ref($request)) {
+ $request->print("Unable to handle ambiguous references:$url:.");
+ }
return ();
}
}
@@ -11847,42 +15098,977 @@ sub clean_symb {
return ($symb,$enc);
}
-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);
- }
+############################################################
+############################################################
+
+=pod
+
+=head1 Routines for building display used to search for courses
+
+
+=over 4
+
+=item * &build_filters()
+
+Create markup for a table used to set filters to use when selecting
+courses in a domain. Used by lonpickcourse.pm, lonmodifycourse.pm
+and quotacheck.pl
+
+
+Inputs:
+
+filterlist - anonymous array of fields to include as potential filters
+
+crstype - course type
+
+roleelement - fifth arg in selectcourse_link() populates fifth arg in javascript: opencrsbrowser() function, used
+ to pop-open a course selector (will contain "extra element").
+
+multelement - if multiple course selections will be allowed, this will be a hidden form element: name: multiple; value: 1
+
+filter - anonymous hash of criteria and their values
+
+action - form action
+
+numfiltersref - ref to scalar (count of number of elements in institutional codes -- e.g., 4 for year, semester, department, and number)
+
+caller - caller context (e.g., set to 'modifycourse' when routine is called from lonmodifycourse.pm)
+
+cloneruname - username of owner of new course who wants to clone
+
+clonerudom - domain of owner of new course who wants to clone
+
+typeelem - text to use for left column in row containing course type (i.e., Course, Community or Course/Community)
+
+codetitlesref - reference to array of titles of components in institutional codes (official courses)
+
+codedom - domain
+
+formname - value of form element named "form".
+
+fixeddom - domain, if fixed.
+
+prevphase - value to assign to form element named "phase" when going back to the previous screen
+
+cnameelement - name of form element in form on opener page which will receive title of selected course
+
+cnumelement - name of form element in form on opener page which will receive courseID of selected course
+
+cdomelement - name of form element in form on opener page which will receive domain of selected course
+
+setroles - includes access constraint identifier when setting a roles-based condition for acces to a portfolio file
+
+clonetext - hidden form elements containing list of courses cloneable by intended course owner when DC creates a course
+
+clonewarning - warning message about missing information for intended course owner when DC creates a course
+
+
+Returns: $output - HTML for display of search criteria, and hidden form elements.
+
+
+Side Effects: None
+
+=cut
+
+# ---------------------------------------------- search for courses based on last activity etc.
+
+sub build_filters {
+ my ($filterlist,$crstype,$roleelement,$multelement,$filter,$action,
+ $numtitlesref,$caller,$cloneruname,$clonerudom,$typeelement,
+ $codetitlesref,$codedom,$formname,$fixeddom,$prevphase,
+ $cnameelement,$cnumelement,$cdomelement,$setroles,
+ $clonetext,$clonewarning) = @_;
+ my ($list,$jscript);
+ my $onchange = 'javascript:updateFilters(this)';
+ my ($domainselectform,$sincefilterform,$createdfilterform,
+ $ownerdomselectform,$persondomselectform,$instcodeform,
+ $typeselectform,$instcodetitle);
+ if ($formname eq '') {
+ $formname = $caller;
+ }
+ foreach my $item (@{$filterlist}) {
+ unless (($item eq 'descriptfilter') || ($item eq 'instcodefilter') ||
+ ($item eq 'sincefilter') || ($item eq 'createdfilter')) {
+ if ($item eq 'domainfilter') {
+ $filter->{$item} = &LONCAPA::clean_domain($filter->{$item});
+ } elsif ($item eq 'coursefilter') {
+ $filter->{$item} = &LONCAPA::clean_courseid($filter->{$item});
+ } elsif ($item eq 'ownerfilter') {
+ $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+ } elsif ($item eq 'ownerdomfilter') {
+ $filter->{'ownerdomfilter'} =
+ &LONCAPA::clean_domain($filter->{$item});
+ $ownerdomselectform = &select_dom_form($filter->{'ownerdomfilter'},
+ 'ownerdomfilter',1);
+ } elsif ($item eq 'personfilter') {
+ $filter->{$item} = &LONCAPA::clean_username($filter->{$item});
+ } elsif ($item eq 'persondomfilter') {
+ $persondomselectform = &select_dom_form($filter->{'persondomfilter'},
+ 'persondomfilter',1);
} else {
- push(@{$checkparms->{$name}},$value);
+ $filter->{$item} =~ s/\W//g;
}
- } elsif ($item eq 'resourcetag') {
- if ($name eq 'responsetype') {
- $checkresponsetypes->{$value} = $Apache::lonnet::needsrelease{$key}
+ if (!$filter->{$item}) {
+ $filter->{$item} = '';
}
- } elsif ($item eq 'course') {
- if ($name eq 'crstype') {
- $checkcrstypes->{$value} = $Apache::lonnet::needsrelease{$key};
+ }
+ if ($item eq 'domainfilter') {
+ my $allow_blank = 1;
+ if ($formname eq 'portform') {
+ $allow_blank=0;
+ } elsif ($formname eq 'studentform') {
+ $allow_blank=0;
+ }
+ if ($fixeddom) {
+ $domainselectform = ''.
+ &Apache::lonnet::domain($codedom,'description');
+ } else {
+ $domainselectform = &select_dom_form($filter->{$item},
+ 'domainfilter',
+ $allow_blank,'',$onchange);
}
+ } else {
+ $list->{$item} = &HTML::Entities::encode($filter->{$item},'<>&"');
}
}
- ($anonsurvey->{major},$anonsurvey->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:anonsurvey'});
- ($randomizetry->{major},$randomizetry->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:randomizetry'});
+
+ # last course activity filter and selection
+ $sincefilterform = &timebased_select_form('sincefilter',$filter);
+
+ # course created filter and selection
+ if (exists($filter->{'createdfilter'})) {
+ $createdfilterform = &timebased_select_form('createdfilter',$filter);
+ }
+
+ my %lt = &Apache::lonlocal::texthash(
+ 'cac' => "$crstype Activity",
+ 'ccr' => "$crstype Created",
+ 'cde' => "$crstype Title",
+ 'cdo' => "$crstype Domain",
+ 'ins' => 'Institutional Code',
+ 'inc' => 'Institutional Categorization',
+ 'cow' => "$crstype Owner/Co-owner",
+ 'cop' => "$crstype Personnel Includes",
+ 'cog' => 'Type',
+ );
+
+ if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) {
+ my $typeval = 'Course';
+ if ($crstype eq 'Community') {
+ $typeval = 'Community';
+ }
+ $typeselectform = '';
+ } else {
+ $typeselectform = '
('.$codedom.')';
+ }
+ }
+ }
+ my $output = qq|
+'."\n".'
'."\n";
+ return $jscript.$clonewarning.$output;
+}
+
+=pod
+
+=item * &timebased_select_form()
+
+Create markup for a dropdown list used to select a time-based
+filter e.g., Course Activity, Course Created, when searching for courses
+or communities
+
+Inputs:
+
+item - name of form element (sincefilter or createdfilter)
+
+filter - anonymous hash of criteria and their values
+
+Returns: HTML for a select box contained a blank, then six time selections,
+ with value set in incoming form variables currently selected.
+
+Side Effects: None
+
+=cut
+
+sub timebased_select_form {
+ my ($item,$filter) = @_;
+ if (ref($filter) eq 'HASH') {
+ $filter->{$item} =~ s/[^\d-]//g;
+ if (!$filter->{$item}) { $filter->{$item}=-1; }
+ return &select_form(
+ $filter->{$item},
+ $item,
+ { '-1' => '',
+ '86400' => &mt('today'),
+ '604800' => &mt('last week'),
+ '2592000' => &mt('last month'),
+ '7776000' => &mt('last three months'),
+ '15552000' => &mt('last six months'),
+ '31104000' => &mt('last year'),
+ 'select_form_order' =>
+ ['-1','86400','604800','2592000','7776000',
+ '15552000','31104000']});
+ }
+}
+
+=pod
+
+=item * &js_changer()
+
+Create script tag containing Javascript used to submit course search form
+when course type or domain is changed, and also to hide 'Searching ...' on
+page load completion for page showing search result.
+
+Inputs: None
+
+Returns: markup containing updateFilters() and hideSearching() javascript functions.
+
+Side Effects: None
+
+=cut
+
+sub js_changer {
+ return <
'.$foldertitle;
+ }
+ if (wantarray) {
+ return ($title,$foldertitle,$renametitle);
+ }
+ return $title;
+}
+
+sub recurse_supplemental {
+ my ($cnum,$cdom,$suppmap,$numfiles,$errors) = @_;
+ if ($suppmap) {
+ my ($errtext,$fatal) = &LONCAPA::map::mapread('/uploaded/'.$cdom.'/'.$cnum.'/'.$suppmap);
+ if ($fatal) {
+ $errors ++;
+ } else {
+ if ($#LONCAPA::map::resources > 0) {
+ foreach my $res (@LONCAPA::map::resources) {
+ my ($title,$src,$ext,$type,$status)=split(/\:/,$res);
+ if (($src ne '') && ($status eq 'res')) {
+ if ($src =~ m{^\Q/uploaded/$cdom/$cnum/\E(supplemental_\d+\.sequence)$}) {
+ ($numfiles,$errors) = &recurse_supplemental($cnum,$cdom,$1,$numfiles,$errors);
+ } else {
+ $numfiles ++;
+ }
+ }
+ }
+ }
+ }
+ }
+ return ($numfiles,$errors);
+}
+
+sub symb_to_docspath {
+ my ($symb) = @_;
+ return unless ($symb);
+ my ($mapurl,$id,$resurl) = &Apache::lonnet::decode_symb($symb);
+ if ($resurl=~/\.(sequence|page)$/) {
+ $mapurl=$resurl;
+ } elsif ($resurl eq 'adm/navmaps') {
+ $mapurl=$env{'course.'.$env{'request.course.id'}.'.url'};
+ }
+ my $mapresobj;
+ my $navmap = Apache::lonnavmaps::navmap->new();
+ if (ref($navmap)) {
+ $mapresobj = $navmap->getResourceByUrl($mapurl);
+ }
+ $mapurl=~s{^.*/([^/]+)\.(\w+)$}{$1};
+ my $type=$2;
+ my $path;
+ if (ref($mapresobj)) {
+ my $pcslist = $mapresobj->map_hierarchy();
+ if ($pcslist ne '') {
+ foreach my $pc (split(/,/,$pcslist)) {
+ next if ($pc <= 1);
+ my $res = $navmap->getByMapPc($pc);
+ if (ref($res)) {
+ my $thisurl = $res->src();
+ $thisurl=~s{^.*/([^/]+)\.\w+$}{$1};
+ my $thistitle = $res->title();
+ $path .= '&'.
+ &Apache::lonhtmlcommon::entity_encode($thisurl).'&'.
+ &escape($thistitle).
+ ':'.$res->randompick().
+ ':'.$res->randomout().
+ ':'.$res->encrypted().
+ ':'.$res->randomorder().
+ ':'.$res->is_page();
+ }
+ }
+ }
+ $path =~ s/^\&//;
+ my $maptitle = $mapresobj->title();
+ if ($mapurl eq 'default') {
+ $maptitle = 'Main Content';
+ }
+ $path .= (($path ne '')? '&' : '').
+ &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
+ &escape($maptitle).
+ ':'.$mapresobj->randompick().
+ ':'.$mapresobj->randomout().
+ ':'.$mapresobj->encrypted().
+ ':'.$mapresobj->randomorder().
+ ':'.$mapresobj->is_page();
+ } else {
+ my $maptitle = &Apache::lonnet::gettitle($mapurl);
+ my $ispage = (($type eq 'page')? 1 : '');
+ if ($mapurl eq 'default') {
+ $maptitle = 'Main Content';
+ }
+ $path = &Apache::lonhtmlcommon::entity_encode($mapurl).'&'.
+ &escape($maptitle).':::::'.$ispage;
+ }
+ unless ($mapurl eq 'default') {
+ $path = 'default&'.
+ &escape('Main Content').
+ ':::::&'.$path;
+ }
+ return $path;
+}
+
+sub captcha_display {
+ my ($context,$lonhost) = @_;
+ my ($output,$error);
+ my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ if ($captcha eq 'original') {
+ $output = &create_captcha();
+ unless ($output) {
+ $error = 'captcha';
+ }
+ } elsif ($captcha eq 'recaptcha') {
+ $output = &create_recaptcha($pubkey);
+ unless ($output) {
+ $error = 'recaptcha';
+ }
+ }
+ return ($output,$error,$captcha);
+}
+
+sub captcha_response {
+ my ($context,$lonhost) = @_;
+ my ($captcha_chk,$captcha_error);
+ my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost);
+ if ($captcha eq 'original') {
+ ($captcha_chk,$captcha_error) = &check_captcha();
+ } elsif ($captcha eq 'recaptcha') {
+ $captcha_chk = &check_recaptcha($privkey);
+ } else {
+ $captcha_chk = 1;
+ }
+ return ($captcha_chk,$captcha_error);
+}
+
+sub get_captcha_config {
+ my ($context,$lonhost) = @_;
+ my ($captcha,$pubkey,$privkey,$hashtocheck);
+ my $hostname = &Apache::lonnet::hostname($lonhost);
+ my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname);
+ my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID);
+ if ($context eq 'usercreation') {
+ my %domconfig = &Apache::lonnet::get_dom('configuration',[$context],$serverhomedom);
+ if (ref($domconfig{$context}) eq 'HASH') {
+ $hashtocheck = $domconfig{$context}{'cancreate'};
+ if (ref($hashtocheck) eq 'HASH') {
+ if ($hashtocheck->{'captcha'} eq 'recaptcha') {
+ if (ref($hashtocheck->{'recaptchakeys'}) eq 'HASH') {
+ $pubkey = $hashtocheck->{'recaptchakeys'}{'public'};
+ $privkey = $hashtocheck->{'recaptchakeys'}{'private'};
+ }
+ if ($privkey && $pubkey) {
+ $captcha = 'recaptcha';
+ } else {
+ $captcha = 'original';
+ }
+ } elsif ($hashtocheck->{'captcha'} ne 'notused') {
+ $captcha = 'original';
+ }
+ }
+ } else {
+ $captcha = 'captcha';
+ }
+ } elsif ($context eq 'login') {
+ my %domconfhash = &Apache::loncommon::get_domainconf($serverhomedom);
+ if ($domconfhash{$serverhomedom.'.login.captcha'} eq 'recaptcha') {
+ $pubkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_public'};
+ $privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'};
+ if ($privkey && $pubkey) {
+ $captcha = 'recaptcha';
+ } else {
+ $captcha = 'original';
+ }
+ } elsif ($domconfhash{$serverhomedom.'.login.captcha'} eq 'original') {
+ $captcha = 'original';
+ }
+ }
+ return ($captcha,$pubkey,$privkey);
+}
+
+sub create_captcha {
+ my %captcha_params = &captcha_settings();
+ my ($output,$maxtries,$tries) = ('',10,0);
+ while ($tries < $maxtries) {
+ $tries ++;
+ my $captcha = Authen::Captcha->new (
+ output_folder => $captcha_params{'output_dir'},
+ data_folder => $captcha_params{'db_dir'},
+ );
+ my $md5sum = $captcha->generate_code($captcha_params{'numchars'});
+
+ if (-e $Apache::lonnet::perlvar{'lonCaptchaDir'}.'/'.$md5sum.'.png') {
+ $output = ''."\n".
+ &mt('Type in the letters/numbers shown below').' '.
+ ''.
+ '
'.
+ '';
+ last;
+ }
+ }
+ return $output;
+}
+
+sub captcha_settings {
+ my %captcha_params = (
+ output_dir => $Apache::lonnet::perlvar{'lonCaptchaDir'},
+ www_output_dir => "/captchaspool",
+ db_dir => $Apache::lonnet::perlvar{'lonCaptchaDb'},
+ numchars => '5',
+ );
+ return %captcha_params;
+}
+
+sub check_captcha {
+ my ($captcha_chk,$captcha_error);
+ my $code = $env{'form.code'};
+ my $md5sum = $env{'form.crypt'};
+ my %captcha_params = &captcha_settings();
+ my $captcha = Authen::Captcha->new(
+ output_folder => $captcha_params{'output_dir'},
+ data_folder => $captcha_params{'db_dir'},
+ );
+ $captcha_chk = $captcha->check_code($code,$md5sum);
+ my %captcha_hash = (
+ 0 => 'Code not checked (file error)',
+ -1 => 'Failed: code expired',
+ -2 => 'Failed: invalid code (not in database)',
+ -3 => 'Failed: invalid code (code does not match crypt)',
+ );
+ if ($captcha_chk != 1) {
+ $captcha_error = $captcha_hash{$captcha_chk}
+ }
+ return ($captcha_chk,$captcha_error);
+}
+
+sub create_recaptcha {
+ my ($pubkey) = @_;
+ my $use_ssl;
+ if ($ENV{'SERVER_PORT'} == 443) {
+ $use_ssl = 1;
+ }
+ my $captcha = Captcha::reCAPTCHA->new;
+ return $captcha->get_options_setter({theme => 'white'})."\n".
+ $captcha->get_html($pubkey,undef,$use_ssl).
+ &mt('If either word is hard to read, [_1] will replace them.',
+ '').
+ '
';
+}
+
+sub check_recaptcha {
+ my ($privkey) = @_;
+ my $captcha_chk;
+ my $captcha = Captcha::reCAPTCHA->new;
+ my $captcha_result =
+ $captcha->check_answer(
+ $privkey,
+ $ENV{'REMOTE_ADDR'},
+ $env{'form.recaptcha_challenge_field'},
+ $env{'form.recaptcha_response_field'},
+ );
+ if ($captcha_result->{is_valid}) {
+ $captcha_chk = 1;
+ }
+ return $captcha_chk;
+}
+
+sub emailusername_info {
+ my @fields = ('firstname','lastname','institution','web','location','officialemail');
+ my %titles = &Apache::lonlocal::texthash (
+ lastname => 'Last Name',
+ firstname => 'First Name',
+ institution => 'School/college/university',
+ location => "School's city, state/province, country",
+ web => "School's web address",
+ officialemail => 'E-mail address at institution (if different)',
+ );
+ return (\@fields,\%titles);
+}
+
+sub cleanup_html {
+ my ($incoming) = @_;
+ my $outgoing;
+ if ($incoming ne '') {
+ $outgoing = $incoming;
+ $outgoing =~ s/;/;/g;
+ $outgoing =~ s/\#/#/g;
+ $outgoing =~ s/\&/&/g;
+ $outgoing =~ s/</g;
+ $outgoing =~ s/>/>/g;
+ $outgoing =~ s/\(/(/g;
+ $outgoing =~ s/\)/)/g;
+ $outgoing =~ s/"/"/g;
+ $outgoing =~ s/'/'/g;
+ $outgoing =~ s/\$/$/g;
+ $outgoing =~ s{/}{/}g;
+ $outgoing =~ s/=/=/g;
+ $outgoing =~ s/\\/\/g
+ }
+ return $outgoing;
+}
+
+# Checks for critical messages and returns a redirect url if one exists.
+# $interval indicates how often to check for messages.
+sub critical_redirect {
+ my ($interval) = @_;
+ if ((time-$env{'user.criticalcheck.time'})>$interval) {
+ my @what=&Apache::lonnet::dump('critical', $env{'user.domain'},
+ $env{'user.name'});
+ &Apache::lonnet::appenv({'user.criticalcheck.time'=>time});
+ my $redirecturl;
+ if ($what[0]) {
+ if (($what[0] ne 'con_lost') && ($what[0]!~/^error\:/)) {
+ $redirecturl='/adm/email?critical=display';
+ my $url=&Apache::lonnet::absolute_url().$redirecturl;
+ return (1, $url);
+ }
+ }
+ }
+ return ();
+}
+
+# Use:
+# my $answer=reply("encrypt:passwd:$udom:$uname:$upass",$tryserver);
+#
+##################################################
+# password associated functions #
+##################################################
+sub des_keys {
+ # Make a new key for DES encryption.
+ # Each key has two parts which are returned separately.
+ # Please note: Each key must be passed through the &hex function
+ # before it is output to the web browser. The hex versions cannot
+ # be used to decrypt.
+ my @hexstr=('0','1','2','3','4','5','6','7',
+ '8','9','a','b','c','d','e','f');
+ my $lkey='';
+ for (0..7) {
+ $lkey.=$hexstr[rand(15)];
+ }
+ my $ukey='';
+ for (0..7) {
+ $ukey.=$hexstr[rand(15)];
+ }
+ return ($lkey,$ukey);
+}
+
+sub des_decrypt {
+ my ($key,$cyphertext) = @_;
+ my $keybin=pack("H16",$key);
+ my $cypher;
+ if ($Crypt::DES::VERSION>=2.03) {
+ $cypher=new Crypt::DES $keybin;
+ } else {
+ $cypher=new DES $keybin;
+ }
+ my $plaintext=
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16))));
+ $plaintext.=
+ $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16))));
+ $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) );
+ return $plaintext;
+}
+
1;
__END__;