--- loncom/interface/loncommon.pm 2022/05/30 13:30:03 1.1075.2.161.2.6 +++ loncom/interface/loncommon.pm 2024/08/22 17:05:49 1.1075.2.161.2.26 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1075.2.161.2.6 2022/05/30 13:30:03 raeburn Exp $ +# $Id: loncommon.pm,v 1.1075.2.161.2.26 2024/08/22 17:05:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -61,7 +61,7 @@ use POSIX qw(strftime mktime); use Apache::lonmenu(); use Apache::lonenc(); use Apache::lonlocal; -use Apache::lonnet(); +use Apache::lonnavmaps(); use HTML::Entities; use Apache::lonhtmlcommon(); use Apache::loncoursedata(); @@ -71,6 +71,7 @@ use Apache::lonuserutils(); use Apache::lonuserstate(); use Apache::courseclassifier(); use LONCAPA qw(:DEFAULT :match); +use LONCAPA::map(); use HTTP::Request; use DateTime::TimeZone; use DateTime::Locale; @@ -430,7 +431,7 @@ sub studentbrowser_javascript { + +ENDJS + +} + +=pod + +=item * &iframe_wrapper_resizejs() + +emits javascript used to handle resizing for a page containing +an iframe, to ensure that the iframe does not obscure any +standard LON-CAPA menu items. + +=back + +=cut + +# +# jQuery to use when iframe is in use and a page resize occurs. +# This script will ensure that the iframe does not obscure any +# standard LON-CAPA inline menus (primary, secondary, and/or +# breadcrumbs and Functions menus. Expects javascript from +# &iframe_wrapper_headjs() to be in head portion of the web page, +# e.g., by inclusion in second arg passed to &start_page(). +# + +sub iframe_wrapper_resizejs { + my $offset = 5; + &get_unprocessed_cgi($ENV{'QUERY_STRING'},['inhibitmenu']); + if (($env{'form.inhibitmenu'} eq 'yes') || ($env{'form.only_body'})) { + $offset = 0; + } + return &Apache::lonhtmlcommon::scripttag(< 'document.cu', + kerb_def_dom => 'MSU.EDU', + @_, + ); + my ($lticheck,$result,$authtype,$autharg,$jscall,$disabled); + my ($authnum,%can_assign) = &get_assignable_auth($in{'domain'}); + if ($in{'readonly'}) { + $disabled = ' disabled="disabled"'; + } + if (defined($in{'curr_authtype'})) { + if ($in{'curr_authtype'} eq 'lti') { + if ($can_assign{'lti'}) { + $lticheck = 'checked="checked" '; + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifyuser') { + $lticheck = ''; + } + } + } else { + $result = &mt('Currently LTI Authenticated.'); + return $result; + } + } + } else { + if ($authnum == 1) { + $authtype = ''; + } + } + if (!$can_assign{'lti'}) { + return; + } elsif ($authtype eq '') { + if (defined($in{'mode'})) { + if ($in{'mode'} eq 'modifycourse') { + if ($authnum == 1) { + $authtype = ''; + } + } + } + } + $jscall = "javascript:changed_radio('lti',$in{'formname'});"; + if (($authtype eq '') && (($in{'mode'} eq 'modifycourse') || ($in{'curr_authtype'} ne 'lti'))) { + $authtype = ''; + } + $autharg = ''; + if ($authtype) { + $result = &mt('[_1] LTI Authenticated', + ''.$autharg); + } else { + $result = ''.&mt('LTI Authenticated').''. + $autharg; + } + return $result; +} + sub get_assignable_auth { my ($dom) = @_; if ($dom eq '') { @@ -3834,6 +4001,30 @@ sub syllabuswrapper { return qq{$linktext}; } +sub aboutme_on { + my ($uname,$udom)=@_; + unless ($uname) { $uname=$env{'user.name'}; } + unless ($udom) { $udom=$env{'user.domain'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $hashkey=$uname.':'.$udom; + my ($aboutme,$cached)=&Apache::lonnet::is_cached_new('aboutme',$hashkey); + if ($cached) { + return $aboutme; + } + $aboutme = &Apache::lonnet::usertools_access($uname,$udom,'aboutme'); + &Apache::lonnet::do_cache_new('aboutme',$hashkey,$aboutme,3600); + return $aboutme; +} + +sub devalidate_aboutme_cache { + my ($uname,$udom)=@_; + if (!$udom) { $udom =$env{'user.domain'}; } + if (!$uname) { $uname=$env{'user.name'}; } + return if ($udom eq 'public' && $uname eq 'public'); + my $id=$uname.':'.$udom; + &Apache::lonnet::devalidate_cache_new('aboutme',$id); +} + # ----------------------------------------------------------------------------- sub track_student_link { @@ -4437,9 +4628,15 @@ sub get_previous_attempt { } $prevattempts.= &end_data_table_row().&end_data_table(); } else { + my $msg; + if ($symb =~ /ext\.tool$/) { + $msg = &mt('No grade passed back.'); + } else { + $msg = &mt('Nothing submitted - no attempts.'); + } $prevattempts= &start_data_table().&start_data_table_row(). - ''.&mt('Nothing submitted - no attempts.').''. + ''.$msg.''. &end_data_table_row().&end_data_table(); } } else { @@ -5534,7 +5731,7 @@ sub get_domainconf { foreach my $host (keys(%{$domconfig{'login'}{$key}})) { if (ref($domconfig{'login'}{$key}{$host}) eq 'HASH') { $designhash{$udom.'.login.'.$key.'_'.$host} = 1; - foreach my $item ('text','img','alt','url','title','notsso') { + foreach my $item ('text','img','alt','url','title','window','notsso') { $designhash{$udom.'.login.'.$key.'_'.$item.'_'.$host} = $domconfig{'login'}{$key}{$host}{$item}; } } @@ -5771,6 +5968,8 @@ Input: (optional) filename from which br If page header is being requested for use in a frameset, then the second (option) argument -- frameset will be true, and the target attribute set for links should be target="_parent". + If $title is supplied as the third arg, that will be used to + the left of the breadcrumbs tail for the current path. Returns: HTML div with CSTR path and recent box To be included on Authoring Space pages @@ -5778,7 +5977,7 @@ Returns: HTML div with CSTR path and rec =cut sub CSTR_pageheader { - my ($trailfile,$frameset) = @_; + my ($trailfile,$frameset,$title) = @_; if ($trailfile eq '') { $trailfile = $env{'request.filename'}; } @@ -5801,10 +6000,17 @@ sub CSTR_pageheader { $lastitem = $thisdisfn; } + if ($title eq '') { + $title = &mt('Authoring Space'); + } + my ($target,$crumbtarget) = (' target="_top"','_top'); if ($frameset) { $target = ' target="_parent"'; $crumbtarget = '_parent'; + } elsif (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) { + $target = ''; + $crumbtarget = ''; } elsif (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'})) { $target = ' target="'.$env{'request.deeplink.target'}.'"'; $crumbtarget = $env{'request.deeplink.target'}; @@ -5813,7 +6019,7 @@ sub CSTR_pageheader { my $output = '
' .&Apache::loncommon::help_open_menu('','',3,'Authoring') #FIXME: Broken? Where is it? - .''.&mt('Authoring Space:').' ' + .''.$title.' ' .'
' .&Apache::lonhtmlcommon::crumbs($uname.'/'.$parentpath,$crumbtarget,'/priv/'.$udom,undef,undef); @@ -5834,6 +6040,147 @@ sub CSTR_pageheader { return $output; } +############################################## +=pod + +=item * &nocodemirror() + +Input: None + +Returns: 1 if CodeMirror is deactivated based on + user's preference, or domain default, + if user indicated use of default. + +=cut + +sub nocodemirror { + my $nocodem = $env{'environment.nocodemirror'}; + unless ($nocodem) { + my %domdefs = &Apache::lonnet::get_domain_defaults($env{'user.domain'}); + if ($domdefs{'nocodemirror'}) { + $nocodem = 'yes'; + } + } + if ($nocodem eq 'yes') { + return 1; + } + return; +} + +############################################## +=pod + +=item * &permitted_editors() + +Input: $uri (optional) + +Returns: %editors hash in which keys are editors + permitted in current Authoring Space, + or in current course for web pages + created in a course. + + Value for each key is 1. Possible keys + are: edit, xml, and daxe. + + For a regular Authoring Space, if no specific + set of editors has been set for the Author + who owns the Authoring Space, then the + domain default will be used. If no domain + default has been set, then the keys will be + edit and xml. + + For a course author, or for web pages created + in a course, if no specific set of editors has + been set for the course, then the domain + course default will be used. If no domain + course default has been set, then the keys + will be edit and xml. + +=cut + +sub permitted_editors { + my ($uri) = @_; + my ($is_author,$is_coauthor,$is_course,$auname,$audom,%editors); + if ($env{'request.role'} =~ m{^au\./}) { + $is_author = 1; + } elsif ($env{'request.role'} =~ m{^(?:ca|aa)\./($match_domain)/($match_username)}) { + ($audom,$auname) = ($1,$2); + if (($audom ne '') && ($auname ne '')) { + if (($env{'user.domain'} eq $audom) && + ($env{'user.name'} eq $auname)) { + $is_author = 1; + } else { + $is_coauthor = 1; + } + } + } elsif ($env{'request.course.id'}) { + my ($cdom,$cnum); + $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + if (($env{'request.editurl'} =~ m{^/priv/\Q$cdom/$cnum\E/}) || + ($env{'request.editurl'} =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}) || + ($uri =~ m{^/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/})) { + $is_course = 1; + } elsif ($env{'request.editurl'} =~ m{^/priv/($match_domain)/($match_username)/}) { + ($audom,$auname) = ($1,$2); + } elsif ($env{'request.uri'} =~ m{^/priv/($match_domain)/($match_username)/}) { + ($audom,$auname) = ($1,$2); + } elsif (($uri eq '/daxesave') && + (($env{'form.path'} =~ m{^/daxeopen/priv/\Q$cdom/$cnum\E/}) || + ($env{'form.path'} =~ m{^/daxeopen/uploaded/\Q$cdom/$cnum\E/(docs|supplemental)/}))) { + $is_course = 1; + } elsif (($uri eq '/daxesave') && + ($env{'form.path'} =~ m{^/daxeopen/priv/($match_domain)/($match_username)/})) { + ($audom,$auname) = ($1,$2); + } + unless ($is_course) { + if (($audom ne '') && ($auname ne '')) { + if (($env{'user.domain'} eq $audom) && + ($env{'user.name'} eq $auname)) { + $is_author = 1; + } else { + $is_coauthor = 1; + } + } + } + } + if ($is_author) { + if (exists($env{'environment.editors'})) { + map { $editors{$_} = 1; } split(/,/,$env{'environment.editors'}); + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + } elsif ($is_coauthor) { + if (exists($env{"environment.internal.editors./$audom/$auname"})) { + map { $editors{$_} = 1; } split(/,/,$env{"environment.internal.editors./$audom/$auname"}); + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + } elsif ($is_course) { + if (exists($env{'course.'.$env{'request.course.id'}.'.internal.crseditors'})) { + map { $editors{$_} = 1; } split(/,/,$env{'course.'.$env{'request.course.id'}.'.internal.crseditors'}); + } else { + my %domdefaults = &Apache::lonnet::get_domain_defaults($env{'course.'.$env{'request.course.id'}.'.domain'}); + if (exists($domdefaults{'crseditors'})) { + map { $editors{$_} = 1; } split(/,/,$domdefaults{'crseditors'}); + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + } + } else { + %editors = ( edit => 1, + xml => 1, + ); + } + return %editors; +} + ############################################### ############################################### @@ -5898,6 +6245,21 @@ Inputs: context, this will contain a reference to hash of items to be included in the page header and/or inline menu. +=item * $menucoll, optional argument, if specific menu collection is in + effect, either set as the default for the course, or set for + the deeplink paramater for $env{'request.deeplink.login'} + then $menucoll will be the number of that collection. + +=item * $menuref, optional argument, reference to a hash, containing the + menu options included for the menu in effect, based on the + configuration for the numbered menu collection in use. + +=item * $showncrumbsref, reference to a scalar. Calls to lonmenu::innerregister + within &bodytag() can result in calls to lonhtmlcommon::breadcrumbs(), + if so, $showncrumbsref is set there to 1, and will propagate back + via &bodytag() to &start_page(), to prevent lonhtmlcommon::breadcrumbs() + being called a second time. + =back Returns: A uniform header for LON-CAPA web pages. @@ -5910,7 +6272,7 @@ other decorations will be returned. sub bodytag { my ($title,$function,$addentries,$bodyonly,$domain,$forcereg, $no_nav_bar,$bgcolor,$no_inline_link,$args,$advtoolsref, - $ltiscope,$ltiuri,$ltimenu,$menucoll,$menuref)=@_; + $ltiscope,$ltiuri,$ltimenu,$menucoll,$menuref,$showncrumbsref)=@_; my $public; if ((($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public')) @@ -6061,10 +6423,33 @@ sub bodytag { $bodytag .= Apache::lonhtmlcommon::scripttag( Apache::lonmenu::utilityfunctions($httphost), 'start'); + my $collapsible; + if ($args->{'collapsible_header'} ne '') { + $collapsible = 1; + my ($menustate,$tiptext,$divclass); + if ($args->{'start_collapsed'}) { + $menustate = 'collapsed'; + $tiptext = 'display'; + $divclass = 'hidden'; + } else { + $menustate = 'expanded'; + $tiptext = 'hide'; + $divclass = 'shown'; + } + my $alttext = &mt('menu state: '.$menustate); + my $tooltip = &mt($tiptext.' standard menus'); + $bodytag .= <<"END"; +
+
+$alttext
+
+END + } unless ($args->{'no_primary_menu'}) { my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref, $args->{'links_disabled'}, - $args->{'links_target'}); + $args->{'links_target'}, + $collapsible); if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) { if ($dc_info) { $dc_info = qq|$dc_info|; @@ -6103,12 +6488,12 @@ sub bodytag { $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); if ($env{'request.state'} eq 'construct') { $bodytag .= &Apache::lonmenu::innerregister($forcereg, - $args->{'bread_crumbs'},'','',$hostname,$ltiscope,$ltiuri); + $args->{'bread_crumbs'},'','',$hostname, + $ltiscope,$ltiuri,$showncrumbsref); } elsif ($forcereg) { $bodytag .= &Apache::lonmenu::innerregister($forcereg,undef, - $args->{'group'}, - $args->{'hide_buttons'}, - $hostname,$ltiscope,$ltiuri); + $args->{'group'},$args->{'hide_buttons'}, + $hostname,$ltiscope,$ltiuri,$showncrumbsref); } else { my $forbodytag; &Apache::lonmenu::prepare_functions($env{'request.noversionuri'}, @@ -6126,7 +6511,11 @@ sub bodytag { $bodytag .= '
'; $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); } - + if ($args->{'collapsible_header'} ne '') { + $bodytag .= $args->{'collapsible_header'}. + '
'. + '
'; + } return $bodytag; } @@ -6251,12 +6640,45 @@ sub endbodytag { } if ( exists( $env{'internal.head.redirect'} ) ) { if (!(ref($args) eq 'HASH' && $args->{'noredirectlink'})) { + my ($endbodyjs,$idattr); + if ($env{'internal.head.to_opener'}) { + my $linkid = 'LC_continue_link'; + $idattr = ' id="'.$linkid.'"'; + my $redirect_for_js = &js_escape($env{'internal.head.redirect'}); + $endbodyjs=< +// + +ENDJS + } $endbodytag= - "
". + "$endbodyjs
". &mt('Continue').''. $endbodytag; } } + if ((ref($args) eq 'HASH') && ($args->{'dashjs'})) { + $endbodytag = &Apache::lonhtmlcommon::dash_to_minus_js().$endbodytag; + } return $endbodytag; } @@ -6341,6 +6763,14 @@ form, .inline { display: inline; } +.LC_menus_content.shown{ + display: block; +} + +.LC_menus_content.hidden { + display: none; +} + .LC_right { text-align:right; } @@ -6361,6 +6791,12 @@ form, .inline { width:400px; } +#LC_collapsible_separator { + border: 1px solid black; + width: 99.9%; + height: 0px; +} + .LC_iframecontainer { width: 98%; margin: 0; @@ -7556,6 +7992,11 @@ fieldset { /* overflow: hidden; */ } +fieldset#LC_selectuser { + margin: 0; + padding: 0; +} + article.geogebraweb div { margin: 0; } @@ -8099,6 +8540,10 @@ a#LC_content_toolbar_edittoplevel { background-image:url(/res/adm/pages/edittoplevel.gif); } +a#LC_content_toolbar_printout { + background-image:url(/res/adm/pages/printout.gif); +} + ul#LC_toolbar li a:hover { background-position: bottom center; } @@ -8237,6 +8682,16 @@ pre.LC_wordwrap { } /* + styles used for response display +*/ +div.LC_radiofoil, div.LC_rankfoil { + margin: .5em 0em .5em 0em; +} +table.LC_itemgroup { + margin-top: 1em; +} + +/* styles used by TTH when "Default set of options to pass to tth/m when converting TeX" in course settings has been set @@ -8257,6 +8712,87 @@ span.roman {font-family: serif; font-sty span.overacc2 {position: relative; left: .8em; top: -1.2ex;} span.overacc1 {position: relative; left: .6em; top: -1.2ex;} +/* + sections with roles, for content only +*/ +section[class^="role-"] { + padding-left: 10px; + padding-right: 5px; + margin-top: 8px; + margin-bottom: 8px; + border: 1px solid #2A4; + border-radius: 5px; + box-shadow: 0px 1px 1px #BBB; +} +section[class^="role-"]>h1 { + position: relative; + margin: 0px; + padding-top: 10px; + padding-left: 40px; +} +section[class^="role-"]>h1:before { + position: absolute; + left: -5px; + top: 5px; +} +section.role-activity>h1:before { + content:url('/adm/daxe/images/section_icons/activity.png'); +} +section.role-advice>h1:before { + content:url('/adm/daxe/images/section_icons/advice.png'); +} +section.role-bibliography>h1:before { + content:url('/adm/daxe/images/section_icons/bibliography.png'); +} +section.role-citation>h1:before { + content:url('/adm/daxe/images/section_icons/citation.png'); +} +section.role-conclusion>h1:before { + content:url('/adm/daxe/images/section_icons/conclusion.png'); +} +section.role-definition>h1:before { + content:url('/adm/daxe/images/section_icons/definition.png'); +} +section.role-demonstration>h1:before { + content:url('/adm/daxe/images/section_icons/demonstration.png'); +} +section.role-example>h1:before { + content:url('/adm/daxe/images/section_icons/example.png'); +} +section.role-explanation>h1:before { + content:url('/adm/daxe/images/section_icons/explanation.png'); +} +section.role-introduction>h1:before { + content:url('/adm/daxe/images/section_icons/introduction.png'); +} +section.role-method>h1:before { + content:url('/adm/daxe/images/section_icons/method.png'); +} +section.role-more_information>h1:before { + content:url('/adm/daxe/images/section_icons/more_information.png'); +} +section.role-objectives>h1:before { + content:url('/adm/daxe/images/section_icons/objectives.png'); +} +section.role-prerequisites>h1:before { + content:url('/adm/daxe/images/section_icons/prerequisites.png'); +} +section.role-remark>h1:before { + content:url('/adm/daxe/images/section_icons/remark.png'); +} +section.role-reminder>h1:before { + content:url('/adm/daxe/images/section_icons/reminder.png'); +} +section.role-summary>h1:before { + content:url('/adm/daxe/images/section_icons/summary.png'); +} +section.role-syntax>h1:before { + content:url('/adm/daxe/images/section_icons/syntax.png'); +} +section.role-warning>h1:before { + content:url('/adm/daxe/images/section_icons/warning.png'); +} + #LC_minitab_header { float:left; width:100%; @@ -8310,7 +8846,13 @@ Inputs: $title - optional title for the 3- whether the side effect should occur (side effect of setting $env{'internal.head.redirect'} to the url - redirected too) + redirected to) + 4- whether the redirect target should be + the opener of the current (pop-up) + window (side effect of setting + $env{'internal.head.to_opener'} to + 1, if true. + 5- whether encrypt check should be skipped domain -> force to color decorate a page for a specific domain function -> force usage of a specific rolish color scheme @@ -8373,15 +8915,45 @@ sub headtag { } } if (ref($args->{'redirect'})) { - my ($time,$url,$inhibit_continue) = @{$args->{'redirect'}}; - $url = &Apache::lonenc::check_encrypt($url); + my ($time,$url,$inhibit_continue,$to_opener,$skip_enc_check) = @{$args->{'redirect'}}; + if (!$skip_enc_check) { + $url = &Apache::lonenc::check_encrypt($url); + } if (!$inhibit_continue) { $env{'internal.head.redirect'} = $url; } - $result.=< +ADDMETA + if ($to_opener) { + $env{'internal.head.to_opener'} = 1; + my $dest = &js_escape($url); + my $timeout = int($time * 1000); + $result .=<<"ENDJS"; + +ENDJS + } else { + $result.=<<"ADDMETA"; ADDMETA + } } else { unless (($args->{'frameset'}) || ($args->{'js_ready'}) || ($args->{'only_body'}) || ($args->{'no_nav_bar'})) { my $requrl = $env{'request.uri'}; @@ -8518,8 +9090,12 @@ OFFLOAD $title = 'The LearningOnline Network with CAPA'; } if (!$args->{'no_auto_mt_title'}) { $title = &mt($title); } - $result .= ' LON-CAPA '.$title.'' - .''; + } else { + $result .= ' LON-CAPA '.$title.''; + } + $result .= "\n".'{'frameset'}) { $result .= ' /'; } @@ -8820,6 +9396,7 @@ sub start_page { } } + my $showncrumbs; if (! exists($args->{'skip_phases'}{'body'}) ) { if ($args->{'frameset'}) { my $attr_string = &make_attr_string($args->{'force_register'}, @@ -8833,7 +9410,7 @@ sub start_page { $args->{'force_register'}, $args->{'no_nav_bar'}, $args->{'bgcolor'}, $args->{'no_inline_link'}, $args, \@advtools, - $ltiscope,$ltiuri,\%ltimenu,$menucoll,\%menu); + $ltiscope,$ltiuri,\%ltimenu,$menucoll,\%menu,\$showncrumbs); } } @@ -8855,6 +9432,7 @@ sub start_page { #Breadcrumbs if (exists($args->{'bread_crumbs'}) or exists($args->{'bread_crumbs_component'})) { + unless ($showncrumbs) { &Apache::lonhtmlcommon::clear_breadcrumbs(); #if any br links exists, add them to the breadcrumbs if (exists($args->{'bread_crumbs'}) and ref($args->{'bread_crumbs'}) eq 'ARRAY') { @@ -8874,12 +9452,20 @@ sub start_page { } else { undef($menulink); } + my $linkprotout; + if ($env{'request.deeplink.login'}) { + my $linkprotout = &Apache::lonmenu::linkprot_exit(); + if ($linkprotout) { + &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$linkprotout); + } + } #if bread_crumbs_component exists show it as headline else show only the breadcrumbs if(exists($args->{'bread_crumbs_component'})){ $result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'},'',$menulink); } else { $result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink); } + } } elsif (($env{'environment.remote'} eq 'on') && ($env{'form.inhibitmenu'} ne 'yes') && ($env{'request.noversionuri'} =~ m{^/res/}) && @@ -9018,6 +9604,50 @@ sub symb_from_tinyurl { } } +sub usable_exttools { + my %tooltypes; + if ($env{'request.course.id'}) { + if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'}) { + if ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'both') { + %tooltypes = ( + crs => 1, + dom => 1, + ); + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'crs') { + $tooltypes{'crs'} = 1; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.exttool'} eq 'dom') { + $tooltypes{'dom'} = 1; + } + } else { + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $crstype = lc($env{'course.'.$env{'request.course.id'}.'.type'}); + if ($crstype eq '') { + $crstype = 'course'; + } + if ($crstype eq 'course') { + if ($env{'course.'.$env{'request.course.id'}.'internal.coursecode'}) { + $crstype = 'official'; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.textbook'}) { + $crstype = 'textbook'; + } elsif ($env{'course.'.$env{'request.course.id'}.'.internal.lti'}) { + $crstype = 'lti'; + } else { + $crstype = 'unofficial'; + } + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom); + if ($domdefaults{$crstype.'domexttool'}) { + $tooltypes{'dom'} = 1; + } + if ($domdefaults{$crstype.'exttool'}) { + $tooltypes{'crs'} = 1; + } + } + } + return %tooltypes; +} + sub wishlist_window { return(<<'ENDWISHLIST');