--- loncom/interface/loncommon.pm 2014/02/19 15:50:56 1.1176 +++ loncom/interface/loncommon.pm 2015/03/09 19:09:34 1.1209 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1176 2014/02/19 15:50:56 raeburn Exp $ +# $Id: loncommon.pm,v 1.1209 2015/03/09 19:09:34 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -69,6 +69,7 @@ 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; @@ -1315,8 +1316,10 @@ sub helpLatexCheatsheet { .&help_open_topic('Other_Symbols',&mt('Other Symbols'),$stayOnPage,undef,600) .''; unless ($not_author) { - $out .= ' ' - .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600) + $out .= '' + .&help_open_topic('Authoring_Output_Tags',&mt('Output Tags'),$stayOnPage,undef,600) + .' ' + .&help_open_topic('Authoring_Multilingual_Problems',&mt('How to create problems in different languages'),$stayOnPage,undef,600) .''; } $out .= ''; # End cheatsheet @@ -1754,6 +1757,241 @@ RESIZE } +sub colorfuleditor_js { + return <<"COLORFULEDIT" + +COLORFULEDIT +} + +sub xmleditor_js { + return < + +XMLEDIT +} + +sub insert_folding_button { + my $curDepth = $Apache::lonxml::curdepth; + my $lastresource = $env{'request.ambiguous'}; + + return ""; +} + =pod =head1 Excel and CSV file utility routines @@ -2362,6 +2600,8 @@ Outputs: =item * $clientinfo +=item * $clientosversion + =back =back @@ -2381,8 +2621,9 @@ sub decode_user_agent { my $clientmathml=''; my $clientunicode='0'; my $clientmobile=0; + my $clientosversion=''; for (my $i=0;$i<=$#browsertype;$i++) { - my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\:/,$browsertype[$i]); + my ($bname,$match,$notmatch,$vreg,$minv,$univ)=split(/\%/,$browsertype[$i]); if (($httpbrowser=~/$match/i) && ($httpbrowser!~/$notmatch/i)) { $clientbrowser=$bname; $httpbrowser=~/$vreg/i; @@ -2402,7 +2643,12 @@ sub decode_user_agent { if ($httpbrowser=~/next/i) { $clientos='next'; } if (($httpbrowser=~/mac/i) || ($httpbrowser=~/powerpc/i)) { $clientos='mac'; } - if ($httpbrowser=~/win/i) { $clientos='win'; } + if ($httpbrowser=~/win/i) { + $clientos='win'; + if ($httpbrowser =~/Windows\s+NT\s+(\d+\.\d+)/i) { + $clientosversion = $1; + } + } if ($httpbrowser=~/embed/i) { $clientos='pda'; } if ($httpbrowser=~/(Android|iPod|iPad|iPhone|webOS|Blackberry|Windows Phone|Opera m(?:ob|in)|Fennec)/i) { $clientmobile=lc($1); @@ -2413,7 +2659,8 @@ sub decode_user_agent { $clientinfo = 'chromeframe-'.$1; } return ($httpbrowser,$clientbrowser,$clientversion,$clientmathml, - $clientunicode,$clientos,$clientmobile,$clientinfo); + $clientunicode,$clientos,$clientmobile,$clientinfo, + $clientosversion); } ############################################################### @@ -3749,7 +3996,7 @@ sub user_lang { =over 4 =item * &get_previous_attempt($symb, $username, $domain, $course, - $getattempt, $regexp, $gradesub) + $getattempt, $regexp, $gradesub, $usec, $identifier) Return string with previous attempt on problem. Arguments: @@ -3771,6 +4018,11 @@ Return string with previous attempt on p =item * $gradesub: routine that processes the string if it matches $regexp +=item * $usec: section of the desired student + +=item * $identifier: counter for student (multiple students one problem) or + problem (one student; whole sequence). + =back The output string is a table containing all desired attempts, if any. @@ -3778,7 +4030,7 @@ The output string is a table containing =cut sub get_previous_attempt { - my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub)=@_; + my ($symb,$username,$domain,$course,$getattempt,$regexp,$gradesub,$usec,$identifier)=@_; my $prevattempts=''; no strict 'refs'; if ($symb) { @@ -3794,7 +4046,7 @@ sub get_previous_attempt { } $prevattempts=&start_data_table().&start_data_table_header_row(); $prevattempts.=''.&mt('History').''; - my (%typeparts,%lasthidden); + my (%typeparts,%lasthidden,%regraded,%hidestatus); my $showsurv=&Apache::lonnet::allowed('vas',$env{'request.course.id'}); foreach my $key (sort(keys(%lasthash))) { my ($ign,@parts) = split(/\./,$key); @@ -3811,6 +4063,18 @@ sub get_previous_attempt { $lasthidden{$ign.'.'.$id} = 1; } } + if ($identifier ne '') { + my $id = join(',',@parts); + if (&Apache::lonnet::EXT("resource.$id.problemstatus",$symb, + $domain,$username,$usec,undef,$course) =~ /^no/) { + $hidestatus{$ign.'.'.$id} = 1; + } + } + } elsif ($data eq 'regrader') { + if (($identifier ne '') && (@parts)) { + my $id = join(',',@parts); + $regraded{$ign.'.'.$id} = 1; + } } } else { if ($#parts == 0) { @@ -3822,17 +4086,60 @@ sub get_previous_attempt { } $prevattempts.=&end_data_table_header_row(); if ($getattempt eq '') { + my (%solved,%resets,%probstatus); + if (($identifier ne '') && (keys(%regraded) > 0)) { + for ($version=1;$version<=$returnhash{'version'};$version++) { + foreach my $id (keys(%regraded)) { + if (($returnhash{$version.':'.$id.'.regrader'}) && + ($returnhash{$version.':'.$id.'.tries'} eq '') && + ($returnhash{$version.':'.$id.'.award'} eq '')) { + push(@{$resets{$id}},$version); + } + } + } + } for ($version=1;$version<=$returnhash{'version'};$version++) { - my @hidden; + my (@hidden,@unsolved); if (%typeparts) { foreach my $id (keys(%typeparts)) { - if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) { + if (($returnhash{$version.':'.$id.'.type'} eq 'anonsurvey') || + ($returnhash{$version.':'.$id.'.type'} eq 'anonsurveycred')) { push(@hidden,$id); + } elsif ($identifier ne '') { + unless (($returnhash{$version.':'.$id.'.type'} eq 'survey') || + ($returnhash{$version.':'.$id.'.type'} eq 'surveycred') || + ($hidestatus{$id})) { + next if ((ref($resets{$id}) eq 'ARRAY') && grep(/^\Q$version\E$/,@{$resets{$id}})); + if ($returnhash{$version.':'.$id.'.solved'} eq 'correct_by_student') { + push(@{$solved{$id}},$version); + } elsif (($returnhash{$version.':'.$id.'.solved'} ne '') && + (ref($solved{$id}) eq 'ARRAY')) { + my $skip; + if (ref($resets{$id}) eq 'ARRAY') { + foreach my $reset (@{$resets{$id}}) { + if ($reset > $solved{$id}[-1]) { + $skip=1; + last; + } + } + } + unless ($skip) { + my ($ign,$partslist) = split(/\./,$id,2); + push(@unsolved,$partslist); + } + } + } } } } $prevattempts.=&start_data_table_row(). - ''.&mt('Transaction [_1]',$version).''; + ''.&mt('Transaction [_1]',$version); + if (@unsolved) { + $prevattempts .= ''; + } + $prevattempts .= ''; if (@hidden) { foreach my $key (sort(keys(%lasthash))) { next if ($key =~ /\.foilorder$/); @@ -4319,23 +4626,20 @@ sub findallcourses { ############################################### sub blockcheck { - my ($setters,$activity,$uname,$udom,$url) = @_; + my ($setters,$activity,$uname,$udom,$url,$is_course) = @_; - if (!defined($udom)) { + if (defined($udom) && defined($uname)) { + # If uname and udom are for a course, check for blocks in the course. + if (($is_course) || (&Apache::lonnet::is_course($udom,$uname))) { + my ($startblock,$endblock,$triggerblock) = + &get_blocks($setters,$activity,$udom,$uname,$url); + return ($startblock,$endblock,$triggerblock); + } + } else { $udom = $env{'user.domain'}; - } - if (!defined($uname)) { $uname = $env{'user.name'}; } - # If uname and udom are for a course, check for blocks in the course. - - if (&Apache::lonnet::is_course($udom,$uname)) { - my ($startblock,$endblock,$triggerblock) = - &get_blocks($setters,$activity,$udom,$uname,$url); - return ($startblock,$endblock,$triggerblock); - } - my $startblock = 0; my $endblock = 0; my $triggerblock = ''; @@ -4345,7 +4649,8 @@ sub blockcheck { # boards, chat or groups, check for blocking in current course only. if (($activity eq 'boards' || $activity eq 'chat' || - $activity eq 'groups') && ($env{'request.course.id'})) { + $activity eq 'groups' || $activity eq 'printout') && + ($env{'request.course.id'})) { foreach my $key (keys(%live_courses)) { if ($key ne $env{'request.course.id'}) { delete($live_courses{$key}); @@ -4609,12 +4914,12 @@ sub parse_block_record { } sub blocking_status { - my ($activity,$uname,$udom,$url) = @_; + my ($activity,$uname,$udom,$url,$is_course) = @_; my %setters; # check for active blocking my ($startblock,$endblock,$triggerblock) = - &blockcheck(\%setters,$activity,$uname,$udom,$url); + &blockcheck(\%setters,$activity,$uname,$udom,$url,$is_course); my $blocked = 0; if ($startblock && $endblock) { $blocked = 1; @@ -4669,13 +4974,13 @@ END_BLOCK ############################################### sub check_ip_acc { - my ($acc)=@_; + my ($acc,$clientip)=@_; &Apache::lonxml::debug("acc is $acc"); if (!defined($acc) || $acc =~ /^\s*$/ || $acc =~/^\s*no\s*$/i) { return 1; } my $allowed=0; - my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'}; + my $ip=$env{'request.host'} || $ENV{'REMOTE_ADDR'} || $clientip; my $name; foreach my $pattern (split(',',$acc)) { @@ -4771,23 +5076,29 @@ sub get_domainconf { if (keys(%{$domconfig{'login'}})) { foreach my $key (keys(%{$domconfig{'login'}})) { if (ref($domconfig{'login'}{$key}) eq 'HASH') { - if ($key eq 'loginvia') { - if (ref($domconfig{'login'}{'loginvia'}) eq 'HASH') { - foreach my $hostname (keys(%{$domconfig{'login'}{'loginvia'}})) { - if (ref($domconfig{'login'}{'loginvia'}{$hostname}) eq 'HASH') { - if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) { - my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'}; - $designhash{$udom.'.login.loginvia'} = $server; - if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') { - - $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'}; - } else { - $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'}; + if (($key eq 'loginvia') || ($key eq 'headtag')) { + if (ref($domconfig{'login'}{$key}) eq 'HASH') { + foreach my $hostname (keys(%{$domconfig{'login'}{$key}})) { + if (ref($domconfig{'login'}{$key}{$hostname}) eq 'HASH') { + if ($key eq 'loginvia') { + if ($domconfig{'login'}{'loginvia'}{$hostname}{'server'}) { + my $server = $domconfig{'login'}{'loginvia'}{$hostname}{'server'}; + $designhash{$udom.'.login.loginvia'} = $server; + if ($domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'} eq 'custom') { + + $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'custompath'}; + } else { + $designhash{$udom.'.login.loginvia_'.$hostname} = $server.':'.$domconfig{'login'}{'loginvia'}{$hostname}{'serverpath'}; + } } - if ($domconfig{'login'}{'loginvia'}{$hostname}{'exempt'}) { - $designhash{$udom.'.login.loginvia_exempt_'.$hostname} = $domconfig{'login'}{'loginvia'}{$hostname}{'exempt'}; + } elsif ($key eq 'headtag') { + if ($domconfig{'login'}{'headtag'}{$hostname}{'url'}) { + $designhash{$udom.'.login.headtag_'.$hostname} = $domconfig{'login'}{'headtag'}{$hostname}{'url'}; } } + if ($domconfig{'login'}{$key}{$hostname}{'exempt'}) { + $designhash{$udom.'.login.'.$key.'_exempt_'.$hostname} = $domconfig{'login'}{$key}{$hostname}{'exempt'}; + } } } } @@ -5151,7 +5462,10 @@ sub bodytag { @design{keys(%$addentries)} = @$addentries{keys(%$addentries)}; # role and realm - my ($role,$realm) = split(/\./,$env{'request.role'},2); + my ($role,$realm) = split(m{\./},$env{'request.role'},2); + if ($realm) { + $realm = '/'.$realm; + } if ($role eq 'ca') { my ($rdom,$rname) = ($realm =~ m{^/($match_domain)/($match_username)$}); $realm = &plainname($rname,$rdom); @@ -6482,6 +6796,7 @@ div.LC_edit_problem_footer div, div.LC_edit_problem_editxml_header, div.LC_edit_problem_editxml_header div { margin-top: 5px; + z-index: 100; } div.LC_edit_problem_header_title { @@ -6499,7 +6814,12 @@ table.LC_edit_problem_header_title { div.LC_edit_problem_discards { float: left; - padding-bottom: 5px; +} + +div.LC_edit_actionbar { + margin: -5px 0px 0px 0px !important; + background-color: $sidebg; + height: 31px; } div.LC_edit_problem_saves { @@ -6520,6 +6840,10 @@ div.LC_edit_problem_saves { margin-left: 40px; } +#LC_edit_problem_codemirror div{ + margin-left: 0px; +} + img.stift { border-width: 0; vertical-align: middle; @@ -6634,7 +6958,6 @@ fieldset > legend { ol.LC_primary_menu { margin: 0; padding: 0; - background-color: $pgbg_or_bgcolor; } ol#LC_PathBreadcrumbs { @@ -6646,23 +6969,48 @@ ol.LC_primary_menu li { 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 a, +ol.LC_primary_menu li p { display: block; margin: 0; padding: 0 5px 0 10px; text-decoration: none; } -ol.LC_primary_menu li ul { +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: 10em; + 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 { +ol.LC_primary_menu li:hover > ul, ol.LC_primary_menu li.hover > ul { display: block; position: absolute; margin: 0; @@ -6671,15 +7019,21 @@ ol.LC_primary_menu li:hover ul, ol.LC_pr } 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:hover li a, ol.LC_primary_menu li.hover li a { - background-color:$data_table_light; +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 { @@ -6687,6 +7041,11 @@ ol.LC_primary_menu li li a: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; @@ -7298,7 +7657,10 @@ sub headtag { ''. &font_settings($args); - my $inhibitprint = &print_suppression(); + my $inhibitprint; + if ($args->{'print_suppress'}) { + $inhibitprint = &print_suppression(); + } if (!$args->{'frameset'}) { $result .= &Apache::lonhtmlcommon::htmlareaheaders(); @@ -7424,7 +7786,7 @@ sub print_suppression { } 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); + my $blocked = &blocking_status('printout',$cnum,$cdom,undef,1); if ($blocked) { my $checkrole = "cm./$cdom/$cnum"; if ($env{'request.course.sec'} ne '') { @@ -7657,10 +8019,12 @@ function set_wishlistlink(title, path) { title = title.replace(/^LON-CAPA /,''); } title = encodeURIComponent(title); + title = title.replace("'","\\\'"); if (!path) { path = location.pathname; } path = encodeURIComponent(path); + path = path.replace("'","\\\'"); Win = window.open('/adm/wishlist?mode=newLink&setTitle='+title+'&setPath='+path, 'wishlistNewLink','width=560,height=350,scrollbars=0'); } @@ -7703,12 +8067,13 @@ var modalWindow = { }; var openMyModal = function(source,width,height,scrolling,transparency,style) { + source = source.replace("'","'"); modalWindow.windowId = "myModal"; modalWindow.width = width; modalWindow.height = height; - modalWindow.content = ""; modalWindow.open(); - }; + }; // END LON-CAPA Internal --> // ]]> @@ -8310,7 +8675,7 @@ role status: active, previous or future. sub check_user_status { my ($udom,$uname,$cdom,$crs,$role,$sec) = @_; my %userinfo = &Apache::lonnet::dump('roles',$udom,$uname); - my @uroles = keys %userinfo; + my @uroles = keys(%userinfo); my $srchstr; my $active_chk = 'none'; my $now = time; @@ -8967,10 +9332,10 @@ sub excess_filesize_warning { } $disk_quota = int($disk_quota * 1000); if (($current_disk_usage + $filesize) > $disk_quota) { - return '

'. + return '

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

'. + '

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

'; } @@ -9423,7 +9788,14 @@ sub personal_data_fieldtitles { sub sorted_inst_types { my ($dom) = @_; - my ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom); + my ($usertypes,$order); + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom); + if (ref($domdefaults{'inststatus'}) eq 'HASH') { + $usertypes = $domdefaults{'inststatus'}{'inststatustypes'}; + $order = $domdefaults{'inststatus'}{'inststatusorder'}; + } else { + ($usertypes,$order) = &Apache::lonnet::retrieve_inst_usertypes($dom); + } my $othertitle = &mt('All users'); if ($env{'request.course.id'}) { $othertitle = &mt('Any users'); @@ -9886,7 +10258,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)"); @@ -10917,11 +11297,11 @@ sub check_for_upload { if ($currsize < $filesize) { my $extra = $filesize - $currsize; if (($current_disk_usage + $extra) > $disk_quota) { - my $msg = ''. + my $msg = '

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

'. + '

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

'; return ('will_exceed_quota',$msg); } } @@ -10930,21 +11310,21 @@ sub check_for_upload { } } if (($current_disk_usage + $filesize) > $disk_quota){ - my $msg = ''. - &mt('Unable to upload [_1]. (size = [_2] kilobytes). Disk quota will be exceeded.',''.$fname.'',$filesize).''. - '
'.&mt('Disk quota is [_1] kilobytes. Your current disk usage is [_2] kilobytes.',$disk_quota,$current_disk_usage); + my $msg = '

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

'. + '

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

'; return ('will_exceed_quota',$msg); } elsif ($found_file) { if ($locked_file) { - my $msg = ''; + my $msg = '

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

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

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

'; return ('existingfile',$msg); } } @@ -11042,7 +11422,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", @@ -11064,13 +11444,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; + } } } } @@ -11084,7 +11487,6 @@ function camtasiaToggle() { for (var i=0; i'.&mt('Filename should end with one of: [_1].', '.zip, .tar, .bz2, .gz, .tar.gz, .tar.bz2, .tgz'); @@ -13660,6 +14062,12 @@ sub assign_category_rows { return $text; } +=pod + +=back + +=cut + ############################################################ ############################################################ @@ -14432,7 +14840,7 @@ sub escape_url { my ($url) = @_; my @urlslices = split(/\//, $url,-1); my $lastitem = &escape(pop(@urlslices)); - return join('/',@urlslices).'/'.$lastitem; + return &HTML::Entities::encode(join('/',@urlslices),"'").'/'.$lastitem; } sub compare_arrays { @@ -14490,6 +14898,17 @@ sub init_user_environment { } } closedir(DIR); +# If there is a undeleted lockfile for the user's paste buffer remove it. + my $namespace = 'nohist_courseeditor'; + my $lockingkey = 'paste'."\0".'locked_num'; + my %lockhash = &Apache::lonnet::get($namespace,[$lockingkey], + $domain,$username); + if (exists($lockhash{$lockingkey})) { + my $delresult = &Apache::lonnet::del($namespace,[$lockingkey],$domain,$username); + unless ($delresult eq 'ok') { + &Apache::lonnet::logthis("Failed to delete paste buffer locking key in $namespace for ".$username.":".$domain." Result was: $delresult"); + } + } } # Give them a new cookie my $id = ($args->{'robot'} ? 'robot'.$args->{'robot'} @@ -14503,8 +14922,8 @@ sub init_user_environment { } # ------------------------------------ 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 @@ -14537,6 +14956,7 @@ 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" => '', @@ -14679,36 +15099,737 @@ 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},'<>&"'); + } + } + + # 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 = '"; + } + + my ($cloneableonlyform,$cloneabletitle); + if (exists($filter->{'cloneableonly'})) { + my $cloneableon = ''; + my $cloneableoff = ' checked="checked"'; + if ($filter->{'cloneableonly'}) { + $cloneableon = $cloneableoff; + $cloneableoff = ''; + } + $cloneableonlyform = ''.(' 'x3).''; + if ($formname eq 'ccrs') { + $cloneabletitle = &mt('Cloneable for [_1]',$cloneruname.':'.$clonerudom); + } else { + $cloneabletitle = &mt('Cloneable by you'); + } + } + my $officialjs; + if ($crstype eq 'Course') { + if (exists($filter->{'instcodefilter'})) { +# if (($fixeddom) || ($formname eq 'requestcrs') || +# ($formname eq 'modifycourse') || ($formname eq 'filterpicker')) { + if ($codedom) { + $officialjs = 1; + ($instcodeform,$jscript,$$numtitlesref) = + &Apache::courseclassifier::instcode_selectors($codedom,'filterpicker', + $officialjs,$codetitlesref); + if ($jscript) { + $jscript = ''."\n"; + } + } + if ($instcodeform eq '') { + $instcodeform = + ''; + $instcodetitle = $lt{'ins'}; + } else { + $instcodetitle = $lt{'inc'}; + } + if ($fixeddom) { + $instcodetitle .= '
('.$codedom.')'; + } + } + } + my $output = qq| +
+ +|; + if ($formname eq 'modifycourse') { + $output .= ''."\n". + ''."\n"; + } elsif ($formname eq 'quotacheck') { + $output .= qq| + + +|; + } else { + my $name_input; + if ($cnameelement ne '') { + $name_input = ''; + } + $output .= qq| + + +$name_input +$roleelement +$multelement +$typeelement +|; + if ($formname eq 'portform') { + $output .= ''."\n"; } } - ($anonsurvey->{major},$anonsurvey->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:anonsurvey'}); - ($randomizetry->{major},$randomizetry->{minor}) = split(/\./,$Apache::lonnet::needsrelease{'parameter:type:randomizetry'}); + if ($fixeddom) { + $output .= ''."\n"; + } + $output .= "
\n".&Apache::lonhtmlcommon::start_pick_box(); + if ($sincefilterform) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cac'}) + .$sincefilterform + .&Apache::lonhtmlcommon::row_closure(); + } + if ($createdfilterform) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'ccr'}) + .$createdfilterform + .&Apache::lonhtmlcommon::row_closure(); + } + if ($domainselectform) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cdo'}) + .$domainselectform + .&Apache::lonhtmlcommon::row_closure(); + } + if ($typeselectform) { + if (($formname eq 'ccrs') || ($formname eq 'requestcrs')) { + $output .= $typeselectform; + } else { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cog'}) + .$typeselectform + .&Apache::lonhtmlcommon::row_closure(); + } + } + if ($instcodeform) { + $output .= &Apache::lonhtmlcommon::row_title($instcodetitle) + .$instcodeform + .&Apache::lonhtmlcommon::row_closure(); + } + if (exists($filter->{'ownerfilter'})) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cow'}). + '
'.&mt('Username').'
'. + '
'.&mt('Domain').'
'. + $ownerdomselectform.'
'. + &Apache::lonhtmlcommon::row_closure(); + } + if (exists($filter->{'personfilter'})) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cop'}). + '
'.&mt('Username').'
'. + '
'.&mt('Domain').'
'. + $persondomselectform.'
'. + &Apache::lonhtmlcommon::row_closure(); + } + if (exists($filter->{'coursefilter'})) { + $output .= &Apache::lonhtmlcommon::row_title(&mt('LON-CAPA course ID')) + .'' + .&Apache::lonhtmlcommon::row_closure(); + } + if ($cloneableonlyform) { + $output .= &Apache::lonhtmlcommon::row_title($cloneabletitle). + $cloneableonlyform.&Apache::lonhtmlcommon::row_closure(); + } + if (exists($filter->{'descriptfilter'})) { + $output .= &Apache::lonhtmlcommon::row_title($lt{'cde'}) + .'' + .&Apache::lonhtmlcommon::row_closure(1); + } + $output .= &Apache::lonhtmlcommon::end_pick_box().'

'.$clonetext."\n". + ''."\n". + '

'."\n".'
'."\n".'
'."\n"; + return $jscript.$clonewarning.$output; +} + +=pod + +=item * &timebased_select_form() + +Create markup for a dropdown list used to select a time-based +filter e.g., Course Activity, Course Created, when searching for courses +or communities + +Inputs: + +item - name of form element (sincefilter or createdfilter) + +filter - anonymous hash of criteria and their values + +Returns: HTML for a select box contained a blank, then six time selections, + with value set in incoming form variables currently selected. + +Side Effects: None + +=cut + +sub timebased_select_form { + my ($item,$filter) = @_; + if (ref($filter) eq 'HASH') { + $filter->{$item} =~ s/[^\d-]//g; + if (!$filter->{$item}) { $filter->{$item}=-1; } + return &select_form( + $filter->{$item}, + $item, + { '-1' => '', + '86400' => &mt('today'), + '604800' => &mt('last week'), + '2592000' => &mt('last month'), + '7776000' => &mt('last three months'), + '15552000' => &mt('last six months'), + '31104000' => &mt('last year'), + 'select_form_order' => + ['-1','86400','604800','2592000','7776000', + '15552000','31104000']}); + } +} + +=pod + +=item * &js_changer() + +Create script tag containing Javascript used to submit course search form +when course type or domain is changed, and also to hide 'Searching ...' on +page load completion for page showing search result. + +Inputs: None + +Returns: markup containing updateFilters() and hideSearching() javascript functions. + +Side Effects: None + +=cut + +sub js_changer { + return < +// + + +ENDJS +} + +=pod + +=item * &search_courses() + +Process selected filters form course search form and pass to lonnet::courseiddump +to retrieve a hash for which keys are courseIDs which match the selected filters. + +Inputs: + +dom - domain being searched + +type - course type ('Course' or 'Community' or '.' if any). + +filter - anonymous hash of criteria and their values + +numtitles - for institutional codes - number of categories + +cloneruname - optional username of new course owner + +clonerudom - optional domain of new course owner + +domcloner - Optional "domcloner" flag; has value=1 if user has ccc priv in domain being filtered by, + (used when DC is using course creation form) + +codetitles - reference to array of titles of components in institutional codes (official courses). + + +Returns: %courses - hash of courses satisfying search criteria, keys = course IDs, values are corresponding colon-separated escaped description, institutional code, owner and type. + + +Side Effects: None + +=cut + + +sub search_courses { + my ($dom,$type,$filter,$numtitles,$cloneruname,$clonerudom,$domcloner,$codetitles) = @_; + my (%courses,%showcourses,$cloner); + if (($filter->{'ownerfilter'} ne '') || + ($filter->{'ownerdomfilter'} ne '')) { + $filter->{'combownerfilter'} = $filter->{'ownerfilter'}.':'. + $filter->{'ownerdomfilter'}; + } + foreach my $item ('descriptfilter','coursefilter','combownerfilter') { + if (!$filter->{$item}) { + $filter->{$item}='.'; + } + } + my $now = time; + my $timefilter = + ($filter->{'sincefilter'}==-1?1:$now-$filter->{'sincefilter'}); + my ($createdbefore,$createdafter); + if (($filter->{'createdfilter'} ne '') && ($filter->{'createdfilter'} !=-1)) { + $createdbefore = $now; + $createdafter = $now-$filter->{'createdfilter'}; + } + my ($instcodefilter,$regexpok); + if ($numtitles) { + if ($env{'form.official'} eq 'on') { + $instcodefilter = + &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles); + $regexpok = 1; + } elsif ($env{'form.official'} eq 'off') { + $instcodefilter = &Apache::courseclassifier::instcode_search_str($dom,$numtitles,$codetitles); + unless ($instcodefilter eq '') { + $regexpok = -1; + } + } + } else { + $instcodefilter = $filter->{'instcodefilter'}; + } + if ($instcodefilter eq '') { $instcodefilter = '.'; } + if ($type eq '') { $type = '.'; } + + if (($clonerudom ne '') && ($cloneruname ne '')) { + $cloner = $cloneruname.':'.$clonerudom; + } + %courses = &Apache::lonnet::courseiddump($dom, + $filter->{'descriptfilter'}, + $timefilter, + $instcodefilter, + $filter->{'combownerfilter'}, + $filter->{'coursefilter'}, + undef,undef,$type,$regexpok,undef,undef, + undef,undef,$cloner,$env{'form.cc_clone'}, + $filter->{'cloneableonly'}, + $createdbefore,$createdafter,undef, + $domcloner); + if (($filter->{'personfilter'} ne '') && ($filter->{'persondomfilter'} ne '')) { + my $ccrole; + if ($type eq 'Community') { + $ccrole = 'co'; + } else { + $ccrole = 'cc'; + } + my %rolehash = &Apache::lonnet::get_my_roles($filter->{'personfilter'}, + $filter->{'persondomfilter'}, + 'userroles',undef, + [$ccrole,'in','ad','ep','ta','cr'], + $dom); + foreach my $role (keys(%rolehash)) { + my ($cnum,$cdom,$courserole) = split(':',$role); + my $cid = $cdom.'_'.$cnum; + if (exists($courses{$cid})) { + if (ref($courses{$cid}) eq 'HASH') { + if (ref($courses{$cid}{roles}) eq 'ARRAY') { + if (!grep(/^\Q$courserole\E$/,@{$courses{$cid}{roles}})) { + push (@{$courses{$cid}{roles}},$courserole); + } + } else { + $courses{$cid}{roles} = [$courserole]; + } + $showcourses{$cid} = $courses{$cid}; + } + } + } + %courses = %showcourses; + } + return %courses; +} + +=pod + +=back + +=head1 Routines for version requirements for current course. + +=over 4 + +=item * &check_release_required() + +Compares required LON-CAPA version with version on server, and +if required version is newer looks for a server with the required version. + +Looks first at servers in user's owen domain; if none suitable, looks at +servers in course's domain are permitted to host sessions for user's domain. + +Inputs: + +$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp) + +$courseid - Course ID of current course + +$rolecode - User's current role in course (for switchserver query string). + +$required - LON-CAPA version needed by course (format: Major.Minor). + + +Returns: + +$switchserver - query string tp append to /adm/switchserver call (if + current server's LON-CAPA version is too old. + +$warning - Message is displayed if no suitable server could be found. + +=cut + +sub check_release_required { + my ($loncaparev,$courseid,$rolecode,$required) = @_; + my ($switchserver,$warning); + if ($required ne '') { + my ($reqdmajor,$reqdminor) = ($required =~ /^(\d+)\.(\d+)$/); + my ($major,$minor) = ($loncaparev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/); + if ($reqdmajor ne '' && $reqdminor ne '') { + my $otherserver; + if (($major eq '' && $minor eq '') || + (($reqdmajor > $major) || (($reqdmajor == $major) && ($reqdminor > $minor)))) { + my ($userdomserver) = &Apache::lonnet::choose_server($env{'user.domain'},undef,$required,1); + my $switchlcrev = + &Apache::lonnet::get_server_loncaparev($env{'user.domain'}, + $userdomserver); + my ($swmajor,$swminor) = ($switchlcrev =~ /^\'?(\d+)\.(\d+)\.[\w.\-]+\'?$/); + if (($swmajor eq '' && $swminor eq '') || ($reqdmajor > $swmajor) || + (($reqdmajor == $swmajor) && ($reqdminor > $swminor))) { + my $cdom = $env{'course.'.$courseid.'.domain'}; + if ($cdom ne $env{'user.domain'}) { + my ($coursedomserver,$coursehostname) = &Apache::lonnet::choose_server($cdom,undef,$required,1); + my $serverhomeID = &Apache::lonnet::get_server_homeID($coursehostname); + my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID); + my %defdomdefaults = &Apache::lonnet::get_domain_defaults($serverhomedom); + my %udomdefaults = &Apache::lonnet::get_domain_defaults($env{'user.domain'}); + my $remoterev = &Apache::lonnet::get_server_loncaparev($serverhomedom,$coursedomserver); + my $canhost = + &Apache::lonnet::can_host_session($env{'user.domain'}, + $coursedomserver, + $remoterev, + $udomdefaults{'remotesessions'}, + $defdomdefaults{'hostedsessions'}); + + if ($canhost) { + $otherserver = $coursedomserver; + } else { + $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'
'. &mt("No suitable server could be found amongst servers in either your own domain or in the course's domain."); + } + } else { + $warning = &mt('Requires LON-CAPA version [_1].',$env{'course.'.$courseid.'.internal.releaserequired'}).'
'.&mt("No suitable server could be found amongst servers in your own domain (which is also the course's domain)."); + } + } else { + $otherserver = $userdomserver; + } + } + if ($otherserver ne '') { + $switchserver = 'otherserver='.$otherserver.'&role='.$rolecode; + } + } + } + return ($switchserver,$warning); +} + +=pod + +=item * &check_release_result() + +Inputs: + +$switchwarning - Warning message if no suitable server found to host session. + +$switchserver - query string to append to /adm/switchserver containing lonHostID + and current role. + +Returns: HTML to display with information about requirement to switch server. + Either displaying warning with link to Roles/Courses screen or + display link to switchserver. + +=cut + +sub check_release_result { + my ($switchwarning,$switchserver) = @_; + my $output = &start_page('Selected course unavailable on this server'). + '

'; + if ($switchwarning) { + $output .= $switchwarning.'
'; + if (&show_course()) { + $output .= &mt('Display courses'); + } else { + $output .= &mt('Display roles'); + } + $output .= ''; + } elsif ($switchserver) { + $output .= &mt('This course requires a newer version of LON-CAPA than is installed on this server.'). + '
'. + ''. + &mt('Switch Server'). + ''; + } + $output .= '

'.&end_page(); + return $output; +} + +=pod + +=item * &needs_coursereinit() + +Determine if course contents stored for user's session needs to be +refreshed, because content has changed since "Big Hash" last tied. + +Check for change is made if time last checked is more than 10 minutes ago +(by default). + +Inputs: + +$loncaparev - Version on current server (format: Major.Minor.Subrelease-datestamp) + +$interval (optional) - Time which may elapse (in s) between last check for content + change in current course. (default: 600 s). + +Returns: an array; first element is: + +=over 4 + +'switch' - if content updates mean user's session + needs to be switched to a server running a newer LON-CAPA version + +'update' - if course session needs to be refreshed (i.e., Big Hash needs to be reloaded) + on current server hosting user's session + +'' - if no action required. + +=back + +If first item element is 'switch': + +second item is $switchwarning - Warning message if no suitable server found to host session. + +third item is $switchserver - query string to append to /adm/switchserver containing lonHostID + and current role. + +otherwise: no other elements returned. + +=back + +=cut + +sub needs_coursereinit { + my ($loncaparev,$interval) = @_; + return() unless ($env{'request.course.id'} && $env{'request.course.tied'}); + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $now = time; + if ($interval eq '') { + $interval = 600; + } + if (($now-$env{'request.course.timechecked'})>$interval) { + my $lastchange = &Apache::lonnet::get_coursechange($cdom,$cnum); + &Apache::lonnet::appenv({'request.course.timechecked'=>$now}); + if ($lastchange > $env{'request.course.tied'}) { + my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired'); + if ($curr_reqd_hash{'internal.releaserequired'} ne '') { + my $required = $env{'course.'.$cdom.'_'.$cnum.'.internal.releaserequired'}; + if ($curr_reqd_hash{'internal.releaserequired'} ne $required) { + &Apache::lonnet::appenv({'course.'.$cdom.'_'.$cnum.'.internal.releaserequired' => + $curr_reqd_hash{'internal.releaserequired'}}); + my ($switchserver,$switchwarning) = + &check_release_required($loncaparev,$cdom.'_'.$cnum,$env{'request.role'}, + $curr_reqd_hash{'internal.releaserequired'}); + if ($switchwarning ne '' || $switchserver ne '') { + return ('switch',$switchwarning,$switchserver); + } + } + } + return ('update'); + } + } + return (); +} + sub update_content_constraints { my ($cdom,$cnum,$chome,$cid) = @_; my %curr_reqd_hash = &Apache::lonnet::userenvironment($cdom,$cnum,'internal.releaserequired'); @@ -15051,7 +16172,7 @@ sub check_recaptcha { } sub emailusername_info { - my @fields = ('lastname','firstname','institution','web','location','officialemail'); + my @fields = ('firstname','lastname','institution','web','location','officialemail'); my %titles = &Apache::lonlocal::texthash ( lastname => 'Last Name', firstname => 'First Name', @@ -15085,6 +16206,26 @@ 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. +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); # @@ -15127,12 +16268,6 @@ sub des_decrypt { return $plaintext; } -=pod - -=back - -=cut - 1; __END__;