--- rat/lonpageflip.pm 2008/12/21 05:00:52 1.75.2.1 +++ rat/lonpageflip.pm 2018/12/29 23:24:58 1.100 @@ -2,7 +2,7 @@ # # Page flip handler # -# $Id: lonpageflip.pm,v 1.75.2.1 2008/12/21 05:00:52 raeburn Exp $ +# $Id: lonpageflip.pm,v 1.100 2018/12/29 23:24:58 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -27,6 +27,8 @@ # http://www.lon-capa.org/ # + + package Apache::lonpageflip; use strict; @@ -34,6 +36,9 @@ use LONCAPA; use Apache::Constants qw(:common :http REDIRECT); use Apache::lonnet; use Apache::loncommon(); +use Apache::lonnavmaps(); +use Apache::lonuserstate; +use Apache::lonlocal; use HTML::TokeParser; use GDBM_File; @@ -79,17 +84,27 @@ sub hash_src { my ($mapid,$resid)=split(/\./,$id); my $symb=&Apache::lonnet::encode_symb($hash{'map_id_'.$mapid}, $resid,$hash{'src_'.$id}); + my $anchor; + if ($hash{'ext_'.$id} eq 'true:') { + if ($hash{'src_'.$id} =~ /(\#.+)$/) { + $anchor = $1; + } + } if ($hash{'encrypted_'.$id}) { return (&Apache::lonenc::encrypted($hash{'src_'.$id}), - &Apache::lonenc::encrypted($symb)); + &Apache::lonenc::encrypted($symb), + $hash{'encrypted_'.$id},$anchor); } - return ($hash{'src_'.$id},$symb); + return ($hash{'src_'.$id},$symb,$hash{'encrypted_'.$id},$anchor); } sub move { my ($next,$endupmap,$direction) = @_; my $safecount=0; my $allowed=0; + my $deeplinkonly=0; + my $prev=$next; + my ($prevmapid)=split(/\./,$next); do { ($next,$endupmap)=&get_next_possible_move($next,$endupmap,$direction); @@ -103,14 +118,35 @@ sub move { my $priv = &Apache::lonnet::allowed('bre',$url,$symb); $allowed = (($priv eq 'F') || ($priv eq '2')); } + $deeplinkonly = 0; + if ($hash{'deeplinkonly_'.$next}) { + my ($value,$level) = split(/:/,$hash{'deeplinkonly_'.$next}); + if ($level eq 'resource') { + $deeplinkonly = 1; + } elsif ($level eq 'map') { + if ($mapid != $prevmapid) { + $deeplinkonly = 1; + } + } + } elsif ($hash{'deeplinkonly_'.$prev}) { + my ($value,$level) = split(/:/,$hash{'deeplinkonly_'.$prev}); + if ($level eq 'resource') { + $deeplinkonly = 1; + } elsif ($level eq 'map') { + if ($mapid != $prevmapid) { + $deeplinkonly = 1; + } + } + } $safecount++; } while ( ($next) && ($next!~/\,/) && ( (!$hash{'src_'.$next}) || ( - (!$env{'request.role.adv'}) - && $hash{'randomout_'.$next} + (!$env{'request.role.adv'}) + && (($hash{'randomout_'.$next}) + || ($deeplinkonly)) ) || (!$allowed) ) @@ -215,26 +251,6 @@ sub get_next_possible_move { return ($next,$mapurl); } -sub navlaunch { - my ($r)=@_; - &Apache::loncommon::content_type($r,'text/html'); - &Apache::loncommon::no_cache($r); - $r->send_http_header; - $r->print(&Apache::loncommon::start_page('Launched')); - $r->print(<Goto first resource

- -

Collapse external navigation window

-ENDNAV - $r->print(&Apache::loncommon::end_page()); -} - sub first_accessible_resource { my $furl; if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'.db', @@ -264,6 +280,77 @@ sub first_accessible_resource { } } +sub first_answerable_ressymb { + my $navmap = Apache::lonnavmaps::navmap->new; + return unless (ref($navmap)); + my $iterator = $navmap->getIterator(undef,undef,undef,1); + return unless (ref($iterator)); + my ($curRes,$result); + while ($curRes = $iterator->next()) { + if (ref($curRes) && $curRes->is_problem()) { + foreach my $part (@{$curRes->parts()}) { + if ($curRes->tries($part) < $curRes->maxtries($part)) { + $result = $curRes->link().'?symb='.$curRes->shown_symb(); + last; + } + } + } + } + if ($result) { + return $result; + } else { + return &first_accessible_resource(); + } +} + +sub check_http_req { + my ($srcref) = @_; + return unless (ref($srcref) eq 'SCALAR'); + my $usehttp; + if ($env{'request.course.id'}) { + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + if (($$srcref =~ m{^\Q/public/$cdom/$cnum/syllabus\E($|\?)}) && + ($ENV{'SERVER_PORT'} == 443) && + ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://})) { + unless (&Apache::lonnet::uses_sts()) { + $$srcref .= (($$srcref =~/\?/)? '&':'?') . 'usehttp=1'; + $usehttp = 1; + } + } elsif (($$srcref =~ m{^\Q/adm/wrapper/ext/\E(?!https:)}) && + ($ENV{'SERVER_PORT'} == 443)) { + unless (&Apache::lonnet::uses_sts()) { + my ($url,$anchor) = ($$srcref =~ /^([^\#]+)(?:|(\#[^\#]+))$/); + $$srcref = $url . (($$srcref =~/\?/)? '&':'?') . 'usehttp=1' .$anchor; + $usehttp = 1; + } + } + } + return $usehttp; +} + +sub reinited_js { + my ($url,$cid,$timeout) = @_; + if (!$timeout) { + $timeout = 0; + } + return <<"END"; + +END +} + # ================================================================ Main Handler sub handler { @@ -280,91 +367,189 @@ sub handler { my %cachehash=(); my $multichoice=0; my %multichoicehash=(); - my ($redirecturl,$redirectsymb); + my %prog_state=(); + my ($redirecturl,$redirectsymb,$enc,$anchor,$deeplinklevel); my $next=''; + my $hostname = $r->hostname(); my @possibilities=(); &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['postdata']); if (($env{'form.postdata'})&&($env{'request.course.fn'})) { - $env{'form.postdata'}=~/(\w+)\:(.*)/; - my $direction=$1; - my $currenturl=$2; + my ($direction,$currenturl) = ($env{'form.postdata'}=~/(\w+)\:(.*)/); if ($currenturl=~m|^/enc/|) { - $currenturl=&Apache::lonenc::unencrypted($currenturl); + $currenturl=&Apache::lonenc::unencrypted($currenturl); } $currenturl=~s/\.\d+\.(\w+)$/\.$1/; + $currenturl=~s/^https?\:\/\///; + $currenturl=~s/^[^\/]+//; + my ($preupdatepos,$last,$reinitcheck); + if ($direction eq 'return') { + if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', + &GDBM_READER(),0640)) { + $last=$hash{'last_known'}; + untie(%hash); + } + } elsif ($direction eq 'firstanswerable') { + my $furl = &first_answerable_ressymb(); + my $usehttp = &check_http_req(\$furl); + if (($usehttp) && ($hostname ne '')) { + $furl='http://'.$hostname.$furl; + } else { + $furl=&Apache::lonnet::absolute_url().$furl; + } + &Apache::loncommon::content_type($r,'text/html'); + $r->header_out(Location => $furl); + return REDIRECT; + } elsif ($direction eq 'endplacement') { + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; + $r->print(&Apache::lonplacementtest::showresult()); + return OK; + } + if ($env{'request.course.id'}) { + # Check if course needs to be re-initialized + my $loncaparev = $r->dir_config('lonVersion'); + ($reinitcheck,my @reinit) = &Apache::loncommon::needs_coursereinit($loncaparev); + if ($reinitcheck eq 'switch') { + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; + $r->print(&Apache::loncommon::check_release_result(@reinit)); + return OK; + } elsif ($reinitcheck eq 'update') { + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $preupdatepos = &Apache::lonnet::symbread($currenturl); + unless ($direction eq 'return') { + if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', + &GDBM_READER(),0640)) { + $last=$hash{'last_known'}; + untie(%hash); + } + } + &Apache::loncommon::content_type($r,'text/html'); + $r->send_http_header; + $r->print(&Apache::loncommon::start_page('Content Changed')); + my $preamble = '
'. + '
'. + &mt('Your course session is being updated because of recent changes by course personnel.'). + ' '.&mt('Please be patient.').'
'. + '
'; + %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($r,undef,$preamble); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Updating course')); + my ($furl,$ferr) = &Apache::lonuserstate::readmap("$cdom/$cnum"); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Finished')); + if ($ferr) { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + my $requrl = $r->uri; + $env{'user.error.msg'}="$requrl:bre:0:0:Course not initialized"; + $env{'user.reinit'} = 1; + return HTTP_NOT_ACCEPTABLE; + } else { + if ($last) { + my ($murl,$id,$fn)=&Apache::lonnet::decode_symb($last); + unless (&Apache::lonnet::symbverify($last,$fn)) { + undef($last); + } + } + } + } + } if ($direction eq 'firstres') { my $furl=&first_accessible_resource(); - &Apache::loncommon::content_type($r,'text/html'); - $r->header_out(Location => - &Apache::lonnet::absolute_url().$furl); - - return REDIRECT; + my $usehttp = &check_http_req(\$furl); + if (($usehttp) && ($hostname ne '')) { + $furl='http://'.$hostname.$furl; + } else { + $furl=&Apache::lonnet::absolute_url().$furl; + } + if ($reinitcheck eq 'update') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print(&reinited_js($furl,$env{'request.course.id'},100)); + $r->print(&Apache::loncommon::end_page()); + return OK; + } else { + &Apache::loncommon::content_type($r,'text/html'); + $r->header_out(Location => $furl); + return REDIRECT; + } } - if ($direction eq 'return' || $direction eq 'navlaunch') { + if ($direction eq 'return') { # -------------------------------------------------------- Return to last known - my $last; - if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', - &GDBM_READER(),0640)) { - $last=$hash{'last_known'}; - untie(%hash); - } - my $newloc; + my ($newloc,$usehttp); if (($last) && (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'.db', &GDBM_READER(),0640))) { my ($murl,$id,$fn)=&Apache::lonnet::decode_symb($last); $id=$hash{'map_pc_'.&Apache::lonnet::clutter($murl)}.'.'.$id; $newloc=$hash{'src_'.$id}; if ($newloc) { - if ($hash{'encrypted_'.$id}) { $newloc=&Apache::lonenc::encrypted($newloc); } - + $usehttp = &check_http_req(\$newloc); + if ($hash{'encrypted_'.$id}) { + $newloc=&Apache::lonenc::encrypted($newloc); + } elsif ($newloc =~ m{^(/adm/wrapper/ext/[^\#]+)(?:|\#([^\#]+))$}) { + my ($url,$anchor) = ($1,$2); + if ($anchor) { + $newloc = $url.(($url=~/\?/)?'&':'?').'symb='.&escape($last); + } + } } else { $newloc='/adm/navmaps'; } untie %hash; } else { $newloc='/adm/navmaps'; - } - if ($newloc eq '/adm/navmaps' && $direction eq 'navlaunch') { - &navlaunch($r); - return OK; - } else { + } + if (($usehttp) && ($hostname ne '')) { + $newloc='http://'.$hostname.$newloc; + } else { + $newloc=&Apache::lonnet::absolute_url().$newloc + } + if ($reinitcheck eq 'update') { + $r->print(&reinited_js($newloc,$env{'request.course.id'},100)); + $r->print(&Apache::loncommon::end_page()); + return OK; + } else { &Apache::loncommon::content_type($r,'text/html'); - $r->header_out(Location => - &Apache::lonnet::absolute_url().$newloc); - + $r->header_out(Location => $newloc); return REDIRECT; - } + } } - $currenturl=~s/^https?\:\/\///; - $currenturl=~s/^[^\/]+//; # # Is the current URL on the map? If not, start with last known URL # + unless (&Apache::lonnet::is_on_map($currenturl)) { - my $last; - if (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', - &GDBM_READER(),0640)) { - $last=$hash{'last_known'}; + if ($preupdatepos) { + undef($preupdatepos); + } elsif (tie(%hash,'GDBM_File',$env{'request.course.fn'}.'_symb.db', + &GDBM_READER(),0640)) { + $last=$hash{'last_known'}; untie(%hash); } + my $newloc; if ($last) { $currenturl=&Apache::lonnet::clutter((&Apache::lonnet::decode_symb($last))[2]); } else { - if ($direction eq 'return') { - &Apache::loncommon::content_type($r,'text/html'); - $r->header_out(Location => - &Apache::lonnet::absolute_url(). - '/adm/noidea.html'); - return REDIRECT; - } else { - &navlaunch($r); - return OK; - } + my $newloc = &Apache::lonnet::absolute_url(). + '/adm/navmaps'; + if ($reinitcheck eq 'update') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print(&reinited_js($newloc,$env{'request.course.id'},100)); + $r->print(&Apache::loncommon::end_page()); + return OK; + } else { + &Apache::loncommon::content_type($r,'text/html'); + $r->header_out(Location => $newloc); + return REDIRECT; + } } } # ------------------------------------------- Do we have any idea where we are? my $position; - if ($position=Apache::lonnet::symbread($currenturl)) { + if ($preupdatepos) { + $position = $preupdatepos; + } else { + $position=Apache::lonnet::symbread($currenturl); + } + if ($position) { # ------------------------------------------------------------------------- Yes my ($startoutmap,$mapnum,$thisurl)=&Apache::lonnet::decode_symb($position); $cachehash{$startoutmap}{$thisurl}=[$thisurl,$mapnum]; @@ -387,7 +572,7 @@ sub handler { @possibilities=split(/\,/,$next); if ($#possibilities==0) { # ---------------------------------------------- Only one possibility, redirect - ($redirecturl,$redirectsymb)=&hash_src($next); + ($redirecturl,$redirectsymb,$enc,$anchor)=&hash_src($next); $cachehash{$endupmap}{$redirecturl}= [$redirecturl,(split(/\./,$next))[1]]; } else { @@ -412,12 +597,15 @@ sub handler { } else { # -------------------------------------------------------------- No place to go $multichoice=-1; + if ($hash{'deeplinkonly_'.$rid}) { + (my $value,$deeplinklevel) = split(/:/,$hash{'deeplinkonly_'.$rid}); + } } # ----------------- The program must come past this point to untie the big hash untie(%hash); # --------------------------------------------------------- Store position info $cachehash{$startoutmap}{'last_direction'}=[$direction,'notasymb']; - foreach my $thismap (keys %cachehash) { + foreach my $thismap (keys(%cachehash)) { my $mapnum=$cachehash{$thismap}->{'mapnum'}; delete($cachehash{$thismap}->{'mapnum'}); &Apache::lonnet::symblist($thismap, @@ -432,41 +620,62 @@ sub handler { if ($direction eq 'back') { &Apache::lonnet::linklog($redirecturl,$currenturl); } -# ------------------------------------------------- Check for critical messages - if ((time-$env{'user.criticalcheck.time'})>300) { - my @what=&Apache::lonnet::dump - ('critical',$env{'user.domain'}, - $env{'user.name'}); - if ($what[0]) { - if (($what[0] ne 'con_lost') && - ($what[0]!~/^error\:/)) { - $redirecturl='/adm/email?critical=display'; - $redirectsymb=''; - } - } - &Apache::lonnet::appenv({'user.criticalcheck.time'=>time}); - } - - &Apache::loncommon::content_type($r,'text/html'); - my $url=&Apache::lonnet::absolute_url().$redirecturl; - $url = &add_get_param($url, { 'symb' => $redirectsymb}); - $r->header_out(Location => $url); - return REDIRECT; +# ------------------------------------- Check for and display critical messages + my ($redirect, $url) = &Apache::loncommon::critical_redirect(300,'flip'); + unless ($redirect) { + my $usehttp = &check_http_req(\$redirecturl); + if (($usehttp) && ($hostname ne '')) { + $url='http://'.$hostname.$redirecturl; + } else { + $url=&Apache::lonnet::absolute_url().$redirecturl; + } + my $addanchor; + if (($anchor ne '') && (!$enc || $env{'request.role.adv'})) { + $addanchor = 1; + $url =~ s/\#.+$//; + } + $url = &add_get_param($url, { 'symb' => $redirectsymb}); + if ($addanchor) { + $url .= $anchor; + } + } + if ($reinitcheck eq 'update') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print(&reinited_js($url,$env{'request.course.id'},100)); + $r->print(&Apache::loncommon::end_page()); + return OK; + } else { + &Apache::loncommon::content_type($r,'text/html'); + $r->header_out(Location => $url); + return REDIRECT; + } } else { # --------------------------------------------------------- There was a problem &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; my %lt=&Apache::lonlocal::texthash('title' => 'End of Sequence', + 'deeplink' => 'No link available', + 'deeplinkres' => + 'Navigation to other content is unavailable when accessing content via deep-linking', + 'deeplinkmap' => + 'You have reached the end of the sequence of available materials for access via deep-linking', 'explain' => 'You have reached the end of the sequence of materials.', 'back' => 'Go Back', - 'nav' => 'Navigate Course Content', + 'nav' => 'Course Contents', 'wherenext' => 'There are several possibilities of where to go next', 'pick' => 'Please click on the the resource you intend to access', 'titleheader' => 'Title', - 'type' => 'Type'); + 'type' => 'Type', + 'update' => 'Content updated', + 'expupdate' => 'As a result of a recent update to the sequence of materials, it is not possible to complete the page flip.', + 'gonav' => 'Go to the Contents page to select a resource to display.', + ); + if (&Apache::loncommon::course_type() eq 'Community') { + $lt{'nav'} = &mt('Community Contents'); + } if ($#possibilities>0) { my $start_page= &Apache::loncommon::start_page('Multiple Resources'); @@ -476,13 +685,18 @@ $start_page

$lt{'pick'}:

- +
ENDSTART foreach my $id (@possibilities) { + my $src = $multichoicehash{'src_'.$id}; + my $usehttp = &check_http_req(\$src); + if (($usehttp) && ($hostname ne '')) { + $src = 'http://'.$hostname.$src; + } $r->print( '
$lt{'titleheader'}$lt{'type'}
'. @@ -492,31 +706,86 @@ ENDSTART } $r->print('
'); } else { - my $start_page= - &Apache::loncommon::start_page('No Resource'); - $r->print(<$lt{'title'} -

$lt{'explain'}

-ENDNONE + if ($reinitcheck) { + if (&Apache::loncommon::course_type() eq 'Community') { + $r->print( + &Apache::loncommon::start_page('Community Contents Updated')); + } else { + $r->print( + &Apache::loncommon::start_page('Course Contents Updated')); + } + $r->print('

'.$lt{'update'}.'

' + .'

'.$lt{'expupdate'}.'
' + .$lt{'gonav'}.'

'); + } else { + if (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') && + (!$env{'request.role.adv'})) { + my ($score,$incomplete) = &Apache::lonplacementtest::check_completion(undef,undef,1); + if ($incomplete) { + $r->print(&Apache::lonplacementtest::showincomplete($incomplete)); + } else { + $r->print(&Apache::lonplacementtest::showresult(1)); + } + } else { + $r->print( + &Apache::loncommon::start_page('No Resource')); + if ($deeplinklevel eq 'resource') { + $r->print('

'.$lt{'deeplink'}.'

' + .'

'.$lt{'deeplinkres'}.'

'); + } elsif ($deeplinklevel eq 'map') { + $r->print('

'.$lt{'title'}.'

' + .'

'.$lt{'deeplinkmap'}.'

'); + } else { + $r->print('

'.$lt{'title'}.'

' + .'

'.$lt{'explain'}.'

'); + } + } + } } - $r->print(< -
  • $lt{'back'}
  • -
  • $lt{'nav'}
  • - -ENDMENU + unless (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') || + ($env{'request.role.adv'})) { + if ($deeplinklevel) { + $r->print( + &Apache::lonhtmlcommon::actionbox( + [''.$lt{'back'}.''])); + } elsif ((!@possibilities) && ($reinitcheck)) { + $r->print( + &Apache::lonhtmlcommon::actionbox( + [''.$lt{'nav'}.'' + ])); + } else { + $r->print( + &Apache::lonhtmlcommon::actionbox( + [''.$lt{'back'}.'', + ''.$lt{'nav'}.'' + ])); + } + + } $r->print(&Apache::loncommon::end_page()); + return OK; } } else { # ------------------------------------------------- Problem, could not tie hash + if ($reinitcheck eq 'update') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print(&Apache::loncommon::end_page()); + } $env{'user.error.msg'}="/adm/flip:bre:0:1:Course Data Missing"; return HTTP_NOT_ACCEPTABLE; } } else { # ---------------------------------------- No, could not determine where we are - $r->internal_redirect('/adm/ambiguous'); + my $newloc = '/adm/ambiguous'; + if ($reinitcheck eq 'update') { + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print(&reinited_js($newloc,$env{'request.course.id'},100)); + $r->print(&Apache::loncommon::end_page()); + } else { + $r->internal_redirect($newloc); + } + return OK; } } else { # -------------------------- Class was not initialized or page fliped strangely @@ -528,6 +797,44 @@ ENDMENU 1; __END__ +=pod + +=head1 NAME + +Apache::lonpageflip + +=head1 SYNOPSIS + +Deals with forward, backward, and other page flips. + +This is part of the LearningOnline Network with CAPA project +described at http://www.lon-capa.org. + +=head1 OVERVIEW + +(empty) + +=head1 SUBROUTINES + +=over cleanup() + +=item addrid() + +=item fullmove() + +=item hash_src() + +=item move() + +=item get_next_possible_move() + +=item first_accessible_resource() + +=item handler() + +=back + +=cut