--- loncom/interface/lonhtmlcommon.pm 2016/11/09 01:58:43 1.377 +++ loncom/interface/lonhtmlcommon.pm 2024/09/17 13:06:49 1.414 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common html routines # -# $Id: lonhtmlcommon.pm,v 1.377 2016/11/09 01:58:43 raeburn Exp $ +# $Id: lonhtmlcommon.pm,v 1.414 2024/09/17 13:06:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -78,7 +78,12 @@ sub java_not_enabled { sub coursepreflink { my ($text,$category)=@_; if (&Apache::lonnet::allowed('opa',$env{'request.course.id'})) { - return '<a target="_top" href="'.&HTML::Entities::encode("/adm/courseprefs?phase=display&actions=$category",'<>&"').'"><span class="LC_setting">'.$text.'</span></a>'; + my $target =' target="_top"'; + if ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) || + (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) { + $target =''; + } + return '<a'.$target.' href="'.&HTML::Entities::encode("/adm/courseprefs?phase=display&actions=$category",'<>&"').'"><span class="LC_setting">'.$text.'</span></a>'; } else { return ''; } @@ -92,7 +97,7 @@ sub raw_href_to_link { sub entity_encode { my ($text)=@_; - return &HTML::Entities::encode($text, '<>&"'); + return &HTML::Entities::encode($text, '\'<>&"'); } sub direct_parm_link { @@ -101,9 +106,14 @@ sub direct_parm_link { $filter=&entity_encode($filter); $part=&entity_encode($part); if (($symb) && (&Apache::lonnet::allowed('opa')) && ($target ne 'tex')) { - return "<a target='_top' href='/adm/parmset?symb=$symb&filter=$filter&part=$part'><span class='LC_setting'>$linktext</span></a>"; + my $target=' target="_top"'; + if ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) || + (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) { + $target=''; + } + return "<a".$target." href=\"/adm/parmset?symb=$symb&filter=$filter&part=$part\"><span class=\"LC_setting\">$linktext</span></a>"; } else { - return $linktext; + return $linktext; } } ############################################## @@ -221,15 +231,16 @@ sub dependencycheck_js { $link = '/adm/dependencies?symb='.&HTML::Entities::encode($symb,'<>&"'); } elsif ($folderpath) { $link = '/adm/dependencies?folderpath='.&HTML::Entities::encode($folderpath,'<>&"'); - $url = $uri; + $url = $uri; } elsif ($uri =~ m{^/public/$match_domain/$match_courseid/syllabus$}) { $link = '/adm/dependencies'; } - $link .= (($link=~/\?/)?'&':'?').'title='. + $link .= (($link=~/\?/)?'&':'?').'title='. &HTML::Entities::encode($title,'<>&"'); if ($url) { $link .= '&url='.&HTML::Entities::encode($url,'<>&"'); } + &js_escape(\$link); return <<ENDJS; <script type="text/javascript"> // <![CDATA[ @@ -407,7 +418,7 @@ sub textbox { ############################################## ############################################## sub checkbox { - my ($name,$checked,$value) = @_; + my ($name,$checked,$value,$special) = @_; my $Str = '<input type="checkbox" name="'.$name.'" '; if (defined($value)) { $Str .= 'value="'.$value.'"'; @@ -415,7 +426,7 @@ sub checkbox { if ($checked) { $Str .= ' checked="checked"'; } - $Str .= ' />'; + $Str .= $special.' />'; return $Str; } @@ -858,13 +869,14 @@ parameter setting wizard. ############################################## sub pjump_javascript_definition { my $Str = <<END; - function pjump(type,dis,value,marker,ret,call,hour,min,sec) { + function pjump(type,dis,value,marker,ret,call,hour,min,sec,extra) { openMyModal("/adm/rat/parameter.html?type="+escape(type) +"&value="+escape(value)+"&marker="+escape(marker) +"&return="+escape(ret) +"&call="+escape(call)+"&name="+escape(dis) +"&defhour="+escape(hour)+"&defmin="+escape(min) - +"&defsec="+escape(sec)+"&modal=1",350,350,'no'); + +"&defsec="+escape(sec)+"&extra="+escape(extra) + +"&modal=1",350,350,'no'); } END return $Str; @@ -1025,11 +1037,15 @@ data structure used for bookkeeping. =item $number_to_do The total number of items being processed. +=item $preamble Optional HTML to display before the progress bar. + =back =back Returns a hash containing the progress state data structure. +If $number_to_do is zero or null, an indeterminate progress bar will +be used. =item &Update_PrgWin() @@ -1118,20 +1134,20 @@ Returns: none # Create progress sub Create_PrgWin { - my ($r,$number_to_do)=@_; + my ($r,$number_to_do,$preamble)=@_; my %prog_state; $prog_state{'done'}=0; $prog_state{'firststart'}=&Time::HiRes::time(); $prog_state{'laststart'}=&Time::HiRes::time(); $prog_state{'max'}=$number_to_do; - &Apache::loncommon::LCprogressbar($r); + &Apache::loncommon::LCprogressbar($r,$prog_state{'max'},$preamble); return %prog_state; } # update progress sub Update_PrgWin { my ($r,$prog_state,$displayString)=@_; - &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString); + &Apache::loncommon::LCprogressbarUpdate($r,undef,$displayString,$$prog_state{'max'}); $$prog_state{'laststart'}=&Time::HiRes::time(); } @@ -1181,7 +1197,7 @@ sub Increment_PrgWin { if ($$prog_state{'max'}) { $percent=int(100.*$current/$$prog_state{'max'}); } - &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo); + &Apache::loncommon::LCprogressbarUpdate($r,$percent,$timeinfo,$$prog_state{'max'}); $$prog_state{'laststart'}=&Time::HiRes::time(); } @@ -1218,6 +1234,12 @@ sub crumbs { } else { $path.='/'; } + if ($path eq '/res/') { + unless (&Apache::lonnet::allowed('bre',$path)) { + $output.="$dir/"; + next; + } + } my $href_path = &HTML::Entities::encode($path,'<>&"'); &Apache::loncommon::inhibit_menu_check(\$href_path); if ($form) { @@ -1295,9 +1317,9 @@ sub htmlareaheaders { ENDEDITOR } $s.=(<<ENDJQUERY); -<script type="text/javascript" src="/adm/jQuery/js/jquery-1.11.3.min.js"></script> -<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.11.4.custom.min.js"></script> -<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.11.4.custom.css" /> +<script type="text/javascript" src="/adm/jQuery/js/jquery-3.7.1.min.js"></script> +<script type="text/javascript" src="/adm/jQuery/js/jquery-ui-1.13.3.custom.min.js"></script> +<link rel="stylesheet" type="text/css" href="/adm/jQuery/css/smoothness/jquery-ui-1.13.3.custom.min.css" /> <script type="text/javascript" src="/adm/jpicker/js/jpicker-1.1.6.min.js" > </script> <link rel="stylesheet" type="text/css" href="/adm/jpicker/css/jPicker-1.1.6.min.css" /> @@ -1334,6 +1356,68 @@ $(document).ready(function(){ });'; } +sub countdown { + + # Code to put a due date countdown in 'duedatecountdown' span. + # This is currently located in the breadcrumb headers. + # note that the dueDateLayout is internatinoalized below. + # Here document is used to support the substitution into the javascript below. + # ..which unforunately necessitates escaping the $'s in the javascript. + # There are several times of importance + # + # serverDueDate - The absolute time at which the problem expires. + # serverTime - The server's time when the problem finished computing. + # clientTime - The client's time...as close to serverTime as possible. + # The clientTime will be slightly later due to + # 1. The latency between problem computation and + # the first network action. + # 2. The time required between the page load-start and the actual + # initial javascript execution that got clientTime. + # These are used as follows: + # The difference between clientTime and serverTime are used to + # correct for differences in clock settings between the browser's system and the + # server's. + # + # The difference between clientTime and the time at which the ready() method + # starts executing is used to estimate latencies for page load and submission. + # Since this is an estimate, it is doubled. The latency estimate + one minute + # is used to determine when the countdown timer turns red to warn the user + # to think about submitting. + + + my $dueDateLayout = &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} [_1]', + "<span id='submitearly'></span>"); + my $early = '- <b>'.&mt('Submit Early').'</b>'; + my $pastdue = '- <b>'.&mt('Past Due').'</b>'; + return <<"JAVASCRIPT"; + + var documentReadyTime; + +\$(document).ready(function() { + if (typeof(dueDate) != "undefined") { + documentReadyTime = (new Date()).getTime(); + \$("#duedatecountdown").countdown({until: dueDate, compact: true, + layout: "$dueDateLayout", + onTick: function (periods) { + var latencyEstimate = (documentReadyTime - clientTime) * 2; + if(\$.countdown.periodsToSeconds(periods) < (300 + latencyEstimate)) { + \$("#submitearly").html("$early"); + if (\$.countdown.periodsToSeconds(periods) < 1) { + \$("#submitearly").html("$pastdue"); + } + } + if(\$.countdown.periodsToSeconds(periods) < (60 + latencyEstimate)) { + \$(this).css("color", "red"); //Highlight last minute. + } + } + }); + } +}); + +JAVASCRIPT + +} + # ----------------------------------------- Script to activate only some fields sub htmlareaselectactive { @@ -1557,62 +1641,11 @@ sub htmlareaselectactive { }); '; - $output .= &color_picker; + $output .= &color_picker(); - # Code to put a due date countdown in 'duedatecountdown' span. - # This is currently located in the breadcrumb headers. - # note that the dueDateLayout is internatinoalized below. - # Here document is used to support the substitution into the javascript below. - # ..which unforunately necessitates escaping the $'s in the javascript. - # There are several times of importance - # - # serverDueDate - The absolute time at which the problem expires. - # serverTime - The server's time when the problem finished computing. - # clientTime - The client's time...as close to serverTime as possible. - # The clientTime will be slightly later due to - # 1. The latency between problem computation and - # the first network action. - # 2. The time required between the page load-start and the actual - # initial javascript execution that got clientTime. - # These are used as follows: - # The difference between clientTime and serverTime are used to - # correct for differences in clock settings between the browser's system and the - # server's. - # - # The difference between clientTime and the time at which the ready() method - # starts executing is used to estimate latencies for page load and submission. - # Since this is an estimate, it is doubled. The latency estimate + one minute - # is used to determine when the countdown timer turns red to warn the user - # to think about submitting. - - my $dueDateLayout = &mt('Due in: {dn} {dl} {hnn}{sep}{mnn}{sep}{snn} [_1]', - "<span id='submitearly'></span>"); - my $early = '- <b>'.&mt('Submit Early').'</b>'; - my $pastdue = '- <b>'.&mt('Past Due').'</b>'; - $output .= <<JAVASCRIPT; + $output .= &countdown(); - var documentReadyTime; - -\$(document).ready(function() { - if (typeof(dueDate) != "undefined") { - documentReadyTime = (new Date()).getTime(); - \$("#duedatecountdown").countdown({until: dueDate, compact: true, - layout: "$dueDateLayout", - onTick: function (periods) { - var latencyEstimate = (documentReadyTime - clientTime) * 2; - if(\$.countdown.periodsToSeconds(periods) < (300 + latencyEstimate)) { - \$("#submitearly").html("$early"); - if (\$.countdown.periodsToSeconds(periods) < 1) { - \$("#submitearly").html("$pastdue"); - } - } - if(\$.countdown.periodsToSeconds(periods) < (60 + latencyEstimate)) { - \$(this).css("color", "red"); //Highlight last minute. - } - } - }); - } -}); + $output .= <<"JAVASCRIPT"; /* This code describes the spellcheck options that will be used for items with class 'spellchecked'. It is necessary for those objects' @@ -1705,14 +1738,31 @@ sub show_return_link { unless ($env{'request.course.id'}) { return 0; } if ($env{'request.noversionuri'}=~m{^/priv/} || $env{'request.uri'}=~m{^/priv/}) { return 1; } - return if ($env{'request.noversionuri'} eq '/adm/supplemental'); + return if (($env{'request.noversionuri'} eq '/adm/supplemental') && + ($env{'form.folder'} ne 'supplemental')); + return if (($env{'form.folderpath'} ne '') && + (($env{'request.noversionuri'} =~ m{^/adm/$match_domain/$match_username/aboutme$}) || + ($env{'request.noversionuri'} =~ m{^/public/$match_domain/$match_courseid/syllabus$}))); return if (($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement') && (!$env{'request.role.adv'})); - if (($env{'request.noversionuri'} =~ m{^/adm/(viewclasslist|navmaps)($|\?)}) + if (($env{'request.noversionuri'} =~ m{^/adm/viewclasslist($|\?)}) || ($env{'request.noversionuri'} =~ m{^/adm/.*/aboutme($|\?)})) { return if ($env{'form.register'}); } + if ((($env{'request.symb'} ne '') || ($env{'form.folderpath'} ne '')) && + ($env{'request.noversionuri'} =~m{^/adm/coursedocs/showdoc/uploaded/($match_domain)/($match_courseid)/(docs|supplemental)/})) { + my ($cdom,$cnum,$area) = ($1,$2,$3); + if (($env{'course.'.$env{'request.course.id'}.'.domain'} eq $cdom) && + ($env{'course.'.$env{'request.course.id'}.'.num'} eq $cnum)) { + if (($env{'request.symb'}) && ($area eq 'docs')) { + my ($map,$resid,$url) = &Apache::lonnet::decode_symb($env{'request.symb'}); + return if ($env{'request.noversionuri'} eq '/adm/coursedocs/showdoc/'.$url); + } elsif (($env{'form.folderpath'}) && ($area eq 'supplemental')) { + return; + } + } + } return (($env{'request.noversionuri'}=~m{^/(res|public)/} && $env{'request.symb'} eq '') || @@ -1721,7 +1771,7 @@ sub show_return_link { (($env{'request.noversionuri'}=~/^\/adm\//) && ($env{'request.noversionuri'}!~/^\/adm\/wrapper\//) && ($env{'request.noversionuri'}!~ - m{^/adm/.*/(smppg|bulletinboard|exttools?)($|\?)}) + m{^/adm/.*/(smppg|bulletinboard|ext\.tool)($|\?)}) )); } @@ -1781,6 +1831,54 @@ clientTime = (new Date()).getTime(); END } +## +# Client-side javascript to convert any dashes in text pasted +# into textbox(es) for numericalresponse item(s) to a standard +# minus, i.e., - . Calls to dash_to_minus_js() in end_problem() +# and in loncommon::endbodytag() for a .page (arg: dashjs => 1) +# +# Will apply to any input tag with class: LC_numresponse_text. +# Currently set in start_textline for numericalresponse items. +# + +sub dash_to_minus_js { + return <<'ENDJS'; + +<script type="text/javascript"> +//<![CDATA[ +//<!-- BEGIN LON-CAPA Internal +document.addEventListener("DOMContentLoaded", (event) => { + const numresp = document.querySelectorAll("input.LC_numresponse_text"); + if (numresp.length > 0) { + Array.from(numresp).forEach((el) => { + el.addEventListener("paste", (e) => { + e.preventDefault(); + e.stopPropagation(); + let p = (e.clipboardData || window.clipboardData).getData("text"); + p.toString(); + var regex; + try + { + regex = new RegExp ("\\p{Dash}", "gu"); + } + catch (e) { regex = new RegExp ("[\\u058A\\u05BE\\u1400\\u1806\\u2010-\\u2015\\u2E3A\\u2E3B\\u2E5D\\u301C\\uFE58\\uFE63\\uFF0D]","g"); } + p = p.replace(regex,'-'); + putInText(p); + }); + }); + } + const putInText = (newText, el = document.activeElement) => { + const [start, end] = [el.selectionStart, el.selectionEnd]; + el.setRangeText(newText, start, end, 'end'); + } +}); +// END LON-CAPA Internal --> +//]]> +</script> + +ENDJS +} + ############################################################ ############################################################ @@ -1817,9 +1915,10 @@ boolean, controls whether to include a l if 'nohelp' don't include the orange help link -=item $css_class +=item $crumbs_style -optional name for the class to apply to the table for CSS +optional style attribute for div containing breadcrumbs +unless called from docs_breadcrumbs =item $no_mt @@ -1840,6 +1939,13 @@ loncommon::help_open_topic() to generate text to include in the link in the optional help item ($topic_help) on the right side of the breadcrumbs row. +=item $links_target + +optionally includes the target (_top, _parent or _self) for (i) initial +$menulink item in the breadcrumbs (if present), (ii) return to last location +(if present), and (iii) help item at the right side of breadcrumbs menu, +created by loncommon::help_open_topic() or loncommon::help_open_menu(). + =back =back @@ -1870,11 +1976,9 @@ returns: nothing my %tools = (); sub breadcrumbs { - my ($component,$component_help,$menulink,$helplink,$css_class,$no_mt, - $CourseBreadcrumbs,$topic_help,$topic_help_text,$crstype) = @_; + my ($component,$component_help,$menulink,$helplink,$crumbs_style,$no_mt, + $CourseBreadcrumbs,$topic_help,$topic_help_text,$links_target) = @_; # - $css_class ||= 'LC_breadcrumbs'; - # Make the faq and bug data cascade my $faq = ''; my $bug = ''; @@ -1886,10 +1990,15 @@ returns: nothing # # The first one should be the course or a menu link if (!defined($menulink)) { $menulink=1; } - if ((($crstype eq 'Placement') || (($env{'request.course.id'}) && - ($env{'course.'.$env{'request.course.id'}.'.type'} eq 'Placement'))) && - (!$env{'request.role.adv'})) { - undef($menulink); + if ($menulink) { + if ($env{'request.course.id'}) { + my ($menucoll,$deeplinkmenu,$menuref) = &Apache::loncommon::menucoll_in_effect(); + if (($menucoll) && (ref($menuref) eq 'HASH')) { + if ($menuref->{'main'} eq 'n') { + undef($menulink); + } + } + } } if ($menulink) { my $description = 'Menu'; @@ -1901,7 +2010,7 @@ returns: nothing $env{'course.'.$env{'request.course.id'}.'.description'}; $no_mt_descr = 1; if ($env{'request.noversionuri'} =~ - m{^/public/($match_domain)/($match_courseid)/syllabus$}) { + m{^/?public/($match_domain)/($match_courseid)/syllabus$}) { unless (($env{'course.'.$env{'request.course.id'}.'.domain'} eq $1) && ($env{'course.'.$env{'request.course.id'}.'.num'} eq $2)) { $description = 'Menu'; @@ -1909,9 +2018,16 @@ returns: nothing } } } + my $target = '_top'; + if ($links_target) { + $target = $links_target; + } elsif ((($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) || + (($env{'request.deeplink.login'}) && ($env{'request.deeplink.target'} eq '_self'))) { + $target=''; + } $menulink = { href =>'/adm/menu', title =>'Go to main menu', - target =>'_top', + target =>$target, text =>$description, no_mt =>$no_mt_descr, }; if($last) { @@ -1929,8 +2045,8 @@ returns: nothing title => &mt('Back to most recent content resource'), class => 'LC_menubuttons_link', }; - if ($env{'request.noversionuri'} eq '/adm/searchcat') { - $hashref->{'target'} = '_top'; + if ($links_target) { + $hashref->{'target'} = $links_target; } $links=&htmltag( 'a','<img src="/res/adm/pages/tolastloc.png" alt="'.$alttext.'" class="LC_icon" />', $hashref); @@ -1981,11 +2097,12 @@ returns: nothing if ($faq ne '' || $component_help ne '' || $bug ne '') { $icons .= &Apache::loncommon::help_open_menu($component, $component_help, - $faq,$bug); + $faq,$bug,'','','','', + $links_target); } if ($topic_help && $topic_help_text) { $icons .= ' '.&Apache::loncommon::help_open_topic($topic_help,&mt($topic_help_text),'', - undef,600); + undef,600,'',$links_target); } # @@ -2015,8 +2132,13 @@ returns: nothing } if (($links ne '') || ($nav_and_tools)) { &render_tools(\$links); - $links = &htmltag('div', $links, - { id => "LC_breadcrumbs" }) unless ($CourseBreadcrumbs) ; + unless ($CourseBreadcrumbs) { + my $args = { id => 'LC_breadcrumbs' }; + if ($crumbs_style ne '') { + $args->{'style'} = $crumbs_style; + } + $links = &htmltag('div', $links, $args); + } } my $adv_tools = 0; if (ref($tools{'advtools'}) eq 'ARRAY') { @@ -2185,7 +2307,7 @@ returns: nothing } # End of scope for @Crumbs sub docs_breadcrumbs { - my ($allowed,$crstype,$contenteditor,$title,$precleared)=@_; + my ($allowed,$crstype,$contenteditor,$title,$precleared,$checklinkprot)=@_; my ($folderpath,@folders,$supplementalflag); @folders = split('&',$env{'form.folderpath'}); if ($env{'form.folderpath'} =~ /^supplemental/) { @@ -2200,19 +2322,21 @@ sub docs_breadcrumbs { my $foldername=shift(@folders); if ($folderpath) {$folderpath.='&';} $folderpath.=$folder.'&'.$foldername; - my $url; + my $url = $env{'request.use_absolute'}; if ($allowed) { - $url = '/adm/coursedocs?folderpath='; + $url .= '/adm/coursedocs?folderpath='; } else { - $url = '/adm/supplemental?folderpath='; + $url .= '/adm/supplemental?folderpath='; } $url .= &escape($folderpath); my $name=&unescape($foldername); # each of randompick number, hidden, encrypted, random order, is_page # are appended with ":"s to the foldername $name=~s/\:(\d*)\:(\w*)\:(\w*):(\d*)\:?(\d*)$//; - unless ($supplementalflag) { - if ($contenteditor) { + if ($contenteditor) { + if ($supplementalflag) { + if ($2) { $ishidden=1; } + } else { if ($1 ne '') { $randompick=$1; } else { @@ -2253,9 +2377,17 @@ sub docs_breadcrumbs { $plain=~s/\>\;\s*$//; } my $menulink = 0; - if (!$allowed && !$contenteditor) { + if (!$allowed && !$contenteditor && !$supplementalflag) { $menulink = 1; } + if ($checklinkprot) { + if ($env{'request.deeplink.login'}) { + my $linkprotout = &Apache::lonmenu::linkprot_exit(); + if ($linkprotout) { + &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$linkprotout); + } + } + } return (&breadcrumbs(undef,undef,$menulink,'nohelp',undef,undef, $contenteditor), $randompick,$ishidden,$isencrypted,$plain, @@ -2629,9 +2761,9 @@ sub course_custom_roles { sub resource_info_box { - my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp)=@_; + my ($symb,$onlyfolderflag,$stuvcurrent,$stuvdisp,$divforres)=@_; my $return=''; - if ($stuvcurrent ne '') { + if (($stuvcurrent ne '') || ($divforres)) { $return = '<div class="LC_left_float">'; } if ($symb) { @@ -2660,7 +2792,7 @@ sub resource_info_box { } else { $return='<p><span class="LC_error">'.&mt('No context provided.').'</span></p>'; } - if ($stuvcurrent ne '') { + if (($stuvcurrent ne '') || ($divforres)) { $return .= '</div>'; } return $return; @@ -3391,12 +3523,18 @@ PARAMSONE if (itemid != null) { itemh = itemid.offsetHeight; } - var primaryheight = document.getElementById('LC_nav_bar').offsetHeight; - var secondaryheight; + var primaryheight = 0; + if (document.getElementById('LC_nav_bar') != null) { + primaryheight = document.getElementById('LC_nav_bar').offsetHeight; + } + var secondaryheight = 0; if (document.getElementById('LC_secondary_menu') != null) { secondaryheight = document.getElementById('LC_secondary_menu').offsetHeight; } - var crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight; + var crumbsheight = 0; + if (document.getElementById('LC_breadcrumbs') != null) { + crumbsheight = document.getElementById('LC_breadcrumbs').offsetHeight; + } var dccidheight = 0; if (document.getElementById('dccid') != null) { dccidheight = document.getElementById('dccid').offsetHeight; @@ -3469,20 +3607,37 @@ THIRD sub javascript_jumpto_resource { my $confirm_switch = &mt("Editing requires switching to the resource's home server.")."\n". &mt('Switch server?'); + my $confirm_new_tab = &mt("Editing requires using the resource's home server.")."\n". + &mt('Open a new browser tab?'); &js_escape(\$confirm_switch); + &js_escape(\$confirm_new_tab); return (<<ENDUTILITY) function go(url) { if (url!='' && url!= null) { currentURL = null; currentSymb= null; - window.location.href=url; + var lcHostname = setLCHost(); + if (lcHostname!='' && lcHostname!= null) { + var RegExp = /^https?\:/; + if (RegExp.test(url)) { + window.location.href=url; + } else { + window.location.href=lcHostname+url; + } + } else { + window.location.href=url; + } } } -function need_switchserver(url) { +function need_switchserver(url,target) { if (url!='' && url!= null) { - if (confirm("$confirm_switch")) { + if (target == '_blank') { + if (confirm("$confirm_new_tab")) { + window.open(url,target); + } + } else if (confirm("$confirm_switch")) { go(url); } } @@ -3494,15 +3649,35 @@ ENDUTILITY } sub jump_to_editres { - my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$folderpath, - $title,$idx,$suppurl,$todocs,$suppanchor) = @_; - my ($jscall,$anchor); + my ($cfile,$home,$switchserver,$forceedit,$forcereg,$symb,$shownsymb, + $folderpath,$title,$hostname,$idx,$suppurl,$todocs,$suppanchor) = @_; + my ($jscall,$anchor,$usehttp,$usehttps,$is_ext,$target); if ($switchserver) { if ($home) { + my $resedit; + if ($cfile =~ m{^/priv/($match_domain)/($match_username)/}) { + my ($audom,$auname) = ($1,$2); + unless (&Apache::lonnet::is_course($audom,$auname)) { + unless ((&Apache::lonnet::will_trust('othcoau',$env{'user.domain'},$audom)) && + (&Apache::lonnet::will_trust('coaurem',$audom,$env{'user.domain'}))) { + return; + } + if (($symb ne '') && ($env{'request.course.id'}) && + (&Apache::lonnet::allowed('mdc',$env{'request.course.id'}))) { + unless (&Apache::lonnet::can_switchserver($env{'user.domain'},$home)) { + $target = '_blank'; + $resedit = 1; + } + } + } + } $cfile = '/adm/switchserver?otherserver='.$home.'&role='. &HTML::Entities::encode($env{'request.role'},'"<>&'); - if ($symb) { - $cfile .= '&symb='.&HTML::Entities::encode($symb,'"<>&'); + if ($shownsymb) { + $cfile .= '&symb='.&HTML::Entities::encode($shownsymb,'"<>&'); + if ($resedit) { + $cfile .= '&edit=1'; + } } elsif ($folderpath) { $cfile .= '&folderpath='.&HTML::Entities::encode($folderpath,'"<>&'); } @@ -3512,13 +3687,41 @@ sub jump_to_editres { if ($forcereg) { $cfile .= '&register=1'; } - $jscall = "need_switchserver('".&Apache::loncommon::escape_single($cfile)."');"; + $jscall = "need_switchserver('".&Apache::loncommon::escape_single($cfile)."','$target')"; } } else { unless ($cfile =~ m{^/priv/}) { - if ($cfile =~ m{^(/adm/wrapper/ext/[^#]+)#([^#]+)$}) { + if ($cfile =~ m{^(/adm/wrapper/ext/([^#]+))(?:|#([^#]+))$}) { $cfile = $1; - $anchor = $2; + my $extlink = $2; + $anchor = $3; + $is_ext = 1; + if (($extlink !~ /^https:/) && ($ENV{'SERVER_PORT'} == 443)) { + unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) { + $usehttp = 1; + } + } elsif ($env{'request.use_absolute'}) { + if ($env{'request.use_absolute'} =~ m{^https://}) { + $usehttps = 1; + } + } + } elsif ($cfile =~ m{^/?public/($match_domain)/($match_courseid)/syllabus}) { + if ($ENV{'SERVER_PORT'} == 443) { + my ($cdom,$cnum) = ($1,$2); + if (($env{'request.course.id'}) && + ($env{'course.'.$env{'request.course.id'}.'.num'} eq $cnum) && + ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $cdom)) { + if ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://}) { + unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl($hostname))) { + $usehttp = 1; + } + } + } + } elsif ($env{'request.use_absolute'}) { + if ($env{'request.use_absolute'} =~ m{^https://}) { + $usehttps = 1; + } + } } if ($symb) { if ($anchor ne '') { @@ -3544,6 +3747,16 @@ sub jump_to_editres { } if ($forceedit) { $cfile .= (($cfile=~/\?/)?'&':'?').'forceedit=1'; + if ($usehttps) { + $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile; + } + } elsif ($usehttp) { + if ($hostname ne '') { + $cfile = 'http://'.$hostname.(($cfile =~ /^\//)? '':'/').$cfile; + } + $cfile .= (($cfile=~/\?/)?'&':'?').'usehttp=1'; + } elsif ($usehttps) { + $cfile = $env{'request.use_absolute'}.(($cfile =~ /^\//)? '':'/').$cfile; } if ($forcereg) { $cfile .= (($cfile=~/\?/)?'&':'?').'register=1'; @@ -3570,15 +3783,18 @@ sub jump_to_editres { # javascript_valid_email # # Generates javascript to validate an e-mail address. -# Returns a javascript function which accetps a form field as argumnent, and +# Returns a javascript function which accepts a form field as argument, and # returns false if field.value does not satisfy two regular expression matches # for a valid e-mail address. Backwards compatible with old browsers without # support for javascript RegExp (just checks for @ in field.value in this case). sub javascript_valid_email { my $scripttag .= <<'END'; -function validmail(field) { +function validmail(field,suffix) { var str = field.value; + if (suffix != '' && suffix != undefined) { + str += suffix; + } if (window.RegExp) { var reg1str = "(@.*@)|(\\.\\.)|(@\\.)|(\\.@)|(^\\.)"; var reg2str = "^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$"; //"