--- rat/lonwrapper.pm 2020/02/16 21:57:49 1.72 +++ rat/lonwrapper.pm 2023/12/31 21:45:03 1.86 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Wrapper for external and binary files as standalone resources # -# $Id: lonwrapper.pm,v 1.72 2020/02/16 21:57:49 raeburn Exp $ +# $Id: lonwrapper.pm,v 1.86 2023/12/31 21:45:03 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -39,13 +39,15 @@ use Apache::lonhtmlcommon(); use Apache::lonextresedit(); use Apache::lonexttool(); use Apache::lonhomework(); +use Apache::lonnavmaps(); use LONCAPA qw(:DEFAULT :match); use HTML::Entities(); +use Digest::MD5(); # ================================================================ Main Handler sub wrapper { my ($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,$linktext,$explanation, - $title,$width,$height) = @_; + $title,$width,$height,$reuse,$is_supp) = @_; my $forcereg; unless ($env{'form.folderpath'}) { @@ -56,6 +58,9 @@ sub wrapper { 'show' => 'Show content in pop-up window', ); + (undef,undef,undef,undef,undef,undef,my $clientmobile) = + &Apache::loncommon::decode_user_agent($r); + my ($anchor,$uselink); if ($is_ext) { if ($env{'form.symb'}) { @@ -66,7 +71,10 @@ sub wrapper { } elsif ($env{'form.anchor'} ne '') { $anchor = '#'.$env{'form.anchor'}; } - unless (($is_pdf) && ($env{'browser.mobile'})) { + if (($is_ext eq 'tab') || ($is_ext eq 'window')) { + $uselink = 1; + } + unless (($is_pdf && $clientmobile) || $uselink) { my $hostname = $r->hostname(); my $lonhost = $r->dir_config('lonHostID'); my $ip = &Apache::lonnet::get_host_ip($lonhost); @@ -74,7 +82,7 @@ sub wrapper { } } - my $noiframe = &Apache::loncommon::modal_link($url.$anchor,$lt{'show'},500,400); + my $noiframe = &Apache::loncommon::modal_link(&HTML::Entities::encode($url.$anchor,'&<>"\''),$lt{'show'},500,400); my $args = {'bgcolor' => '#FFFFFF'}; if ($forcereg) { $args->{'force_register'} = $forcereg; @@ -87,9 +95,11 @@ sub wrapper { } if ($env{'form.only_body'}) { $args->{'only_body'} = $env{'form.only_body'}; + } elsif (($is_supp) && ($env{'form.folderpath'} ne '')) { + $args->{'bread_crumbs_nomenu'} = 1; } - my ($countdown,$donemsg); + my ($countdown,$donemsg,$headjs); if (($exttool) && (&Apache::lonnet::EXT('resource.0.gradable') =~ /^yes$/i)) { $Apache::lonhomework::browse = &Apache::lonnet::allowed('bre',$url); if ($env{'form.markaccess'}) { @@ -135,35 +145,103 @@ sub wrapper { } } - my $startpage = &Apache::loncommon::start_page('Menu',undef,$args).$countdown.$donemsg; +# +# Where iframe is in use, if window.onload() executes before the custom resize function +# has been defined (jQuery), two global javascript vars (LCnotready and LCresizedef) +# are used to ensure document.ready() triggers a call to resize, so the iframe contents +# do not obscure the Functions menu. +# + + unless ($clientmobile || ($exttool eq 'window') || ($exttool eq 'tab') || $uselink) { + $headjs = &Apache::loncommon::iframe_wrapper_headjs(); + } + + my $startpage = &Apache::loncommon::start_page('Menu',$headjs,$args).$countdown.$donemsg; my $endpage = &Apache::loncommon::end_page(); if (($uselink) && ($title eq '')) { if ($env{'form.symb'}) { $title=&Apache::lonnet::gettitle($env{'form.symb'}); - } else { + } elsif (!$is_supp) { my $symb=&Apache::lonnet::symbread($r->uri); if ($symb) { $title=&Apache::lonnet::gettitle($symb); } } } - if (($env{'browser.mobile'}) || ($exttool eq 'window') || ($exttool eq 'tab')) { + if ($clientmobile || ($exttool eq 'window') || ($exttool eq 'tab') || + ($is_ext eq 'tab') || ($is_ext eq 'window')) { my $output = $startpage; if ($is_pdf) { $linktext = &mt('Link to PDF (for mobile devices)'); $output .= &create_link($url,$anchor,$title,$linktext); - } elsif (($exttool eq 'window') || ($exttool eq 'tab')) { + } elsif (($exttool eq 'window') || ($exttool eq 'tab') || + ($is_ext eq 'tab') || ($is_ext eq 'window')) { + my $preamble; if ($linktext eq '') { - $linktext = &mt('Launch External Tool'); + if ($exttool) { + $linktext = &mt('Launch External Tool'); + } else { + $linktext = &mt('Link to External Resource'); + } } - $url = &HTML::Entities::encode($url,'"<>&'); - if ($exttool eq 'tab') { - $output .= '
'. - ''. + if ($exttool) { + $url = &HTML::Entities::encode($url,'"<>&'); + } else { + $url = &HTML::Entities::encode($url.$anchor,'&<>"'); + } + if (($exttool eq 'tab') || ($is_ext eq 'tab')) { + my $target; + if ($exttool) { + $target = 'LCExternalToolTab'; + } else { + if ($reuse) { + $target = 'LCExternalResTab'; + } else { + $target = '_blank'; + } + if ($title ne '') { + $preamble = ''.$title.'
'; + } + } + $output .= '
'.$preamble. + ''. $linktext.''. '
'; } else { + my ($target,$extlinkimg); + if ($exttool) { + $target = 'LCExternalToolPopUp'; + } else { + $target = 'LCExternalResPopUp'; + if ($title ne '') { + $preamble = ''.$title.'
'; + } + $extlinkimg = ''; + unless ($reuse) { + my $resid; + if ($env{'request.course.id'}) { + unless (($is_supp) || ($env{'form.folderpath'} =~ /^supplemental/)) { + my $symb=&Apache::lonnet::symbread($r->uri); + if ($symb) { + my $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + my $res = $navmap->getBySymb($symb); + if (ref($res)) { + $resid = $res->id; + $resid =~ s/\./_/g; + } + } + } + } + } + if ($resid eq '') { + $resid = substr(Digest::MD5::md5_hex(Digest::MD5::md5_hex(time(). {}. rand(). $$)), 0, 8); + } + &js_escape(\$resid); + $target .= $resid; + } + } $output .= <<"ENDLINK"; -
- -$linktext +
$preamble + +$linktext$extlinkimg
ENDLINK } - if ($explanation ne '') { - $output .= '
'.$explanation.'
'; - } - if (&Apache::lonnet::EXT('resource.0.gradable')) { - $output .= &Apache::lonfeedback::list_discussion('tool','OPEN'); + if ($exttool) { + if ($explanation ne '') { + $output .= '
'.$explanation.'
'; + } + if (&Apache::lonnet::EXT('resource.0.gradable')) { + $output .= &Apache::lonfeedback::list_discussion('tool','OPEN'); + } } } else { if ($uselink) { @@ -216,37 +296,8 @@ ENDLINK $linktext = &mt('Link to resource'); return $startpage.&create_link($url,$anchor,$title,$linktext).$endpage; } else { - my $offset = 5; - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['inhibitmenu']); - if ($env{'form.inhibitmenu'} eq 'yes') { - $offset = 0; - } - my $script = &Apache::lonhtmlcommon::scripttag(<"'); return <uri; my ($is_ext,$brcrum,$absolute,$is_pdf,$exttool,$cdom,$cnum,$hostname, - $linktext,$explanation,$width,$height); + $linktext,$explanation,$width,$height,$reuse,$is_supp); for ($url){ s|^/adm/wrapper||; - $is_ext = $_ =~ s|^/ext/|http://|; - s|http://https://?|https://| if ($is_ext); + $is_ext = $_ =~ s|^/ext/|http://|; + s|http://https://|https://| if ($is_ext); s|:|:|g; } if ($url =~ /\.pdf$/i) { $is_pdf = 1; + } elsif (($is_ext) && ($env{'request.course.id'})) { + if ($env{'course.'.$env{'request.course.id'}.'.extresource'}) { + (my $selected,$reuse,$width,$height) = split(/:/,$env{'course.'.$env{'request.course.id'}.'.extresource'}); + if ($selected eq 'tab') { + $is_ext = 'tab'; + $width = ''; + $height = ''; + } elsif ($selected eq 'window') { + $is_ext = 'window'; + unless ($width =~ /^\d+$/) { + $width = ''; + } + unless ($height =~ /^\d+$/) { + $height = ''; + } + } else { + $width = ''; + $height = ''; + $reuse = ''; + } + } } elsif ($url =~ m{^/adm/($match_domain)/($match_courseid)/(\d+)/ext\.tool$}) { $cdom = $1; $cnum = $2; @@ -308,9 +380,16 @@ sub handler { my %toolhash = &Apache::lonnet::get('exttool_'.$marker,['target','linktext','explanation','id','width','height'], $cdom,$cnum); if ($toolhash{'id'}) { - my %ltitools = &Apache::lonnet::get_domain_lti($cdom,'consumer'); - if (ref($ltitools{$toolhash{'id'}}) eq 'HASH') { - $exttoolremote = $ltitools{$toolhash{'id'}}{'url'}; + my ($idx,%ltitools); + if ($toolhash{'id'} =~ /^c(\d+)$/) { + $idx = $1; + %ltitools = &Apache::lonnet::get_course_lti($cnum,$cdom,'consumer'); + } else { + $idx = $toolhash{'id'}; + %ltitools = &Apache::lonnet::get_domain_lti($cdom,'consumer'); + } + if (ref($ltitools{$idx}) eq 'HASH') { + $exttoolremote = $ltitools{$idx}{'url'}; } } if ($toolhash{'target'} eq 'window') { @@ -329,7 +408,7 @@ sub handler { } if (($is_ext) || ($exttool)) { &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, - ['forceedit','register','folderpath','symb','idx','title','anchor']); + ['forceedit','register','folderpath','symb','idx','title','anchor','only_body']); if (($env{'form.forceedit'}) && (&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) && (($env{'form.folderpath'} =~ /^supplemental/) || @@ -343,6 +422,7 @@ sub handler { if ($env{'form.anchor'} ne '') { $url .= '#'.$env{'form.anchor'}; } + $is_supp = 1; } my $type = 'ext'; if ($exttool) { @@ -366,8 +446,37 @@ sub handler { $title = &mt('External Tool'); } } - $brcrum = - &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); + $title = &HTML::Entities::encode($title,'\'"<>&'); + $is_supp = 1; + if ($env{'request.course.id'}) { + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom); + } + unless ($env{'form.only_body'}) { + $brcrum = + &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); + } + } + } elsif ($env{'request.course.id'}) { + my $courseurl = &Apache::lonnet::courseid_to_courseurl($env{'request.course.id'}); + $courseurl =~ s{^/}{}; + if ($url =~ m{^\Q/uploaded/$courseurl/supplemental/\E}) { + $is_supp = 1; + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['folderpath','title','only_body']); + if ($env{'form.folderpath'}) { + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + &Apache::loncommon::validate_folderpath(1,'',$cnum,$cdom); + } + my $title = $env{'form.title'}; + $title = &HTML::Entities::encode($title,'\'"<>&'); + my $crstype = &Apache::loncommon::course_type(); + unless ($env{'form.only_body'}) { + $brcrum = + &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); + } } } @@ -400,7 +509,8 @@ sub handler { } $r->print( wrapper($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool, - $linktext,$explanation,undef,$width,$height) ); + $linktext,$explanation,undef,$width,$height,$reuse, + $is_supp) ); } # not just the menu @@ -427,7 +537,9 @@ described at http://www.lon-capa.org. =over -=item wrapper($r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,$linktext,$explanation,$title,$width,$height) +=item wrapper() + +Args: $r,$url,$brcrum,$absolute,$is_ext,$is_pdf,$exttool,$linktext,$explanation,$title,$width,$height,$reuse,$is_supp =over @@ -470,7 +582,11 @@ in Firefox 23 and later, when serving fr =item $is_ext -true if URL is for an external resource. +true if URL is for an external resource. Default true value +is 1 (display in iframe, unless $uselink is true). +If external resource is to be displayed in a tab, +value of $is_ext will be tab, if to be displayed in a pop-up window, +value of $is_ext will be window. =item $is_pdf @@ -482,20 +598,20 @@ If URL is for an External Tool, will con =item $linktext -optional. If URL is for an External Tool, and target type is window or tab, +optional. If URL is for an External Tool, and target type is window or tab, then the link text may be an option set in the course for each tool instance, or may be a default defined in the domain for all instances of the tool. =item $explanation -optional. If URL is for an External Tool, and target type is window or tab, +optional. If URL is for an External Tool, and target type is window or tab, then the explanation is an option set in the course for each tool instance, or may be a default defined in the domain for all instances of the tool. =item $title -optional. If wrapped item is a PDF, and $env{'browser.mobile'} -is true, a link to a PDF is shown. The "title" will be displayed +optional. If wrapped item is a PDF, and $clientmobile is true, +a link to a PDF is shown. The "title" will be displayed above the link, but if not provided as an arg, $env{'form.title'} will be used, otherwise, the filename will be displayed (unless hidden URL set for the resource). @@ -503,41 +619,81 @@ hidden URL set for the resource). =item $width optional. If URL is for an External Tool, and target type is window, -then a default width may have been defined in the domain for all instances of +then a default width may have been defined in the domain for all instances of the tool. If so, that width will be used for the window opened (via a link) -to launch the external tool. +to launch the external tool. If the URL is for an External Resource, and +$is_ext is window, then a default width (px) may have been defined in the current +course for all external resource instances. =item $height optional. If URL is for an External Tool, and target type is window, -then a default height may have been defined in the domain for all instances of +then a default height may have been defined in the domain for all instances of the tool. If so, that height will be used for the window opened (via a link) -to launch the external tool. +to launch the external tool. If the URL is for an External Resource, and +$is_ext is window, then a default height (px) may have been defined in the current +course for all external resource instances. + +=item $reuse + +optional. If the URL is for an External Resource, and $is_ext is tab or window, +then $reuse will be true if the same tab or window is to be reused for display +of all external resource instances in a a course. =back Returns markup for the entire page. +=item create_link() + +Args: $url,$anchor,$title,$linktext + +=over + +=item $url + +URL to link to + +=item $anchor + +optional. Will be the anchor to append to the URL, if there is one. + +=item $title + +optional. If provided will be the title displayed above the link. If +one is not provided, then $env{'form.title'} will be used, unless empty. + +=item $linktext + +The text to inlcude between the and tags. + +=back + +Returns HTML needed to show a link which can be followed to display the +part of the original URL following the initial /adm/wrapper part +in the original request, in the case where an iframe is not going to be +used to embed the content available at the URL in the existing page. + =item handler() Content handler for requests for: /adm/wrapper/... used for content to be displayed in an iframe, or launched in a separate tab or window via a link. The target URL is extracted from the requested URL, by -removing the /adm/wrapper prefix. +removing the /adm/wrapper prefix. -The target URL will typically be a PDF served from the current server, an +The target URL will typically be a PDF served from the current server, an external resource URL served from a different server, or an external tool (from an LTI Provider) launched from LON-CAPA (as LTI Consumer) and launched via a link. If the request included forceedit in the query string, and the requester has -rights to modify course content, then the editor will be didplayed to allow +rights to modify course content, then the editor will be displayed to allow changes to be made to the resource (e.g., change the URL of the external resource, -or change the setting for the external tool instance. +or change the setting for the external tool instance). If not in edit mode, then the wrapper() subroutine will be called to generate the standard LON-CAPA inline menu, and then either a link to launch a separate tab or -window, or an iframe to display the content inline. +window, or an iframe to display the content inline. =back