--- loncom/interface/loncommon.pm 2021/09/25 20:35:26 1.1367 +++ loncom/interface/loncommon.pm 2024/10/08 21:15:42 1.1441 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1367 2021/09/25 20:35:26 raeburn Exp $ +# $Id: loncommon.pm,v 1.1441 2024/10/08 21:15:42 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,7 +71,9 @@ use Apache::lonuserutils(); use Apache::lonuserstate(); use Apache::courseclassifier(); use LONCAPA qw(:DEFAULT :match); +use LONCAPA::ltiutils; use LONCAPA::LWPReq; +use LONCAPA::map(); use HTTP::Request; use DateTime::TimeZone; use DateTime::Locale; @@ -435,7 +437,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(< '. &mt('Filter: [_1]', @@ -3648,6 +3932,170 @@ sub check_passwd_rules { return $warning; } +sub passwd_validation_js { + my ($currpasswdval,$domain,$context,$id) = @_; + my (%passwdconf,$alertmsg); + if ($context eq 'linkprot') { + my %domconfig = &Apache::lonnet::get_dom('configuration',['ltisec'],$domain); + if (ref($domconfig{'ltisec'}) eq 'HASH') { + if (ref($domconfig{'ltisec'}{'rules'}) eq 'HASH') { + %passwdconf = %{$domconfig{'ltisec'}{'rules'}}; + } + } + if ($id eq 'add') { + $alertmsg = &mt('Secret for added launcher did not satisfy requirement(s):').'\n\n'; + } elsif ($id =~ /^\d+$/) { + my $pos = $id+1; + $alertmsg = &mt('Secret for launcher [_1] did not satisfy requirement(s):','#'.$pos).'\n\n'; + } else { + $alertmsg = &mt('A secret did not satisfy requirement(s):').'\n\n'; + } + } elsif ($context eq 'ltitools') { + my %domconfig = &Apache::lonnet::get_dom('configuration',['toolsec'],$domain); + if (ref($domconfig{'toolsec'}) eq 'HASH') { + if (ref($domconfig{'toolsec'}{'rules'}) eq 'HASH') { + %passwdconf = %{$domconfig{'toolsec'}{'rules'}}; + } + } + if ($id eq 'add') { + $alertmsg = &mt('Secret for added external tool did not satisfy requirement(s):').'\n\n'; + } elsif ($id =~ /^\d+$/) { + my $pos = $id+1; + $alertmsg = &mt('Secret for external tool [_1] did not satisfy requirement(s):','#'.$pos).'\n\n'; + } else { + $alertmsg = &mt('A secret did not satisfy requirement(s):').'\n\n'; + } + } else { + %passwdconf = &Apache::lonnet::get_passwdconf($domain); + $alertmsg = &mt('Initial password did not satisfy requirement(s):').'\n\n'; + } + my ($min,$max,@chars,$numrules,$intargjs,%alert); + $numrules = 0; + $min = $Apache::lonnet::passwdmin; + if (ref($passwdconf{'chars'}) eq 'ARRAY') { + if ($passwdconf{'min'} =~ /^\d+$/) { + if ($passwdconf{'min'} > $min) { + $min = $passwdconf{'min'}; + } + } + if ($passwdconf{'max'} =~ /^\d+$/) { + $max = $passwdconf{'max'}; + $numrules ++; + } + @chars = @{$passwdconf{'chars'}}; + if (@chars) { + $numrules ++; + } + } + if ($min > 0) { + $numrules ++; + } + if (($min > 0) || ($max ne '') || (@chars > 0)) { + if ($min) { + $alert{'min'} = &mt('minimum [quant,_1,character]',$min).'\n'; + } + if ($max) { + $alert{'max'} = &mt('maximum [quant,_1,character]',$max).'\n'; + } + my (@charalerts,@charrules); + if (@chars) { + if (grep(/^uc$/,@chars)) { + push(@charalerts,&mt('contain at least one upper case letter')); + push(@charrules,'uc'); + } + if (grep(/^lc$/,@chars)) { + push(@charalerts,&mt('contain at least one lower case letter')); + push(@charrules,'lc'); + } + if (grep(/^num$/,@chars)) { + push(@charalerts,&mt('contain at least one number')); + push(@charrules,'num'); + } + if (grep(/^spec$/,@chars)) { + push(@charalerts,&mt('contain at least one non-alphanumeric')); + push(@charrules,'spec'); + } + } + $intargjs = qq| var rulesmsg = '';\n|. + qq| var currpwval = $currpasswdval;\n|; + if ($min) { + $intargjs .= qq| + if (currpwval.length < $min) { + rulesmsg += ' - $alert{min}'; + } +|; + } + if ($max) { + $intargjs .= qq| + if (currpwval.length > $max) { + rulesmsg += ' - $alert{max}'; + } +|; + } + if (@chars > 0) { + my $charrulestr = '"'.join('","',@charrules).'"'; + my $charalertstr = '"'.join('","',@charalerts).'"'; + $intargjs .= qq| var brokerules = new Array();\n|. + qq| var charrules = new Array($charrulestr);\n|. + qq| var charalerts = new Array($charalertstr);\n|; + my %rules; + map { $rules{$_} = 1; } @chars; + if ($rules{'uc'}) { + $intargjs .= qq| + var ucRegExp = /[A-Z]/; + if (!ucRegExp.test(currpwval)) { + brokerules.push('uc'); + } +|; + } + if ($rules{'lc'}) { + $intargjs .= qq| + var lcRegExp = /[a-z]/; + if (!lcRegExp.test(currpwval)) { + brokerules.push('lc'); + } +|; + } + if ($rules{'num'}) { + $intargjs .= qq| + var numRegExp = /[0-9]/; + if (!numRegExp.test(currpwval)) { + brokerules.push('num'); + } +|; + } + if ($rules{'spec'}) { + $intargjs .= q| + var specRegExp = /[!"#$%&'()*+,\-.\/:;<=>?@[\\^\]_`{\|}~]/; + if (!specRegExp.test(currpwval)) { + brokerules.push('spec'); + } +|; + } + $intargjs .= qq| + if (brokerules.length > 0) { + for (var i=0; i{$id}) eq 'HASH') { + my $range = $ipaccessref->{$id}->{'ip'}; + if ($range) { + if (&Apache::lonnet::ip_match($clientip,$range)) { + if (ref($ipaccessref->{$id}->{'commblocks'}) eq 'HASH') { + if ($ipaccessref->{$id}->{'commblocks'}->{$activity} eq 'on') { + return ('','','',$id,$dom); + last; + } + } + } + } + } + } + } + } + if (($activity eq 'wishlist') || ($activity eq 'annotate')) { + return (); + } + } 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))) { @@ -5218,7 +5772,10 @@ sub blockcheck { my $startblock = 0; my $endblock = 0; my $triggerblock = ''; - my %live_courses = &findallcourses(undef,$uname,$udom); + my %live_courses; + unless (($activity eq 'wishlist') || ($activity eq 'annotate')) { + %live_courses = &findallcourses(undef,$uname,$udom); + } # If uname is for a user, and activity is course-specific, i.e., # boards, chat or groups, check for blocking in current course only. @@ -5503,14 +6060,17 @@ sub parse_block_record { } sub blocking_status { - my ($activity,$uname,$udom,$url,$is_course,$symb,$caller) = @_; + my ($activity,$clientip,$uname,$udom,$url,$is_course,$symb,$caller) = @_; my %setters; # check for active blocking - my ($startblock,$endblock,$triggerblock) = - &blockcheck(\%setters,$activity,$uname,$udom,$url,$is_course,$symb,$caller); + if ($clientip eq '') { + $clientip = &Apache::lonnet::get_requestor_ip(); + } + my ($startblock,$endblock,$triggerblock,$by_ip,$blockdom) = + &blockcheck(\%setters,$activity,$clientip,$uname,$udom,$url,$is_course,$symb,$caller); my $blocked = 0; - if ($startblock && $endblock) { + if (($startblock && $endblock) || ($by_ip)) { $blocked = 1; } @@ -5564,6 +6124,10 @@ END_MYBLOCK $text = &mt('Checking Course Update Blocked'); } elsif ($activity eq 'about') { $text = &mt('Access to User Information Pages Blocked'); + } elsif ($activity eq 'wishlist') { + $text = &mt('Access to Stored Links Blocked'); + } elsif ($activity eq 'annotate') { + $text = &mt('Access to Annotations Blocked'); } $output .= <<"END_BLOCK";
@@ -5750,7 +6314,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}; } } @@ -5860,8 +6424,12 @@ sub domainlogo { &Apache::lonnet::repcopy($local_name); } $imgsrc = &lonhttpdurl($imgsrc); - } - return ''.$domain.''; + } + my $alttext = $domain; + if ($designhash{$domain.'.login.alttext_domlogo'} ne '') { + $alttext = $designhash{$domain.'.login.alttext_domlogo'}; + } + return ''; } elsif (defined(&Apache::lonnet::domain($domain,'description'))) { return &Apache::lonnet::domain($domain,'description'); } else { @@ -5979,6 +6547,12 @@ sub head_subbox { Input: (optional) filename from which breadcrumb trail is built. In most cases no input as needed, as $env{'request.filename'} is appropriate for use in building the breadcrumb trail. + frameset flag + 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 @@ -5986,7 +6560,7 @@ Returns: HTML div with CSTR path and rec =cut sub CSTR_pageheader { - my ($trailfile) = @_; + my ($trailfile,$frameset,$title) = @_; if ($trailfile eq '') { $trailfile = $env{'request.filename'}; } @@ -6009,20 +6583,28 @@ sub CSTR_pageheader { $lastitem = $thisdisfn; } - my ($crsauthor,$title); + my $crsauthor; if (($env{'request.course.id'}) && ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) && ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) { $crsauthor = 1; - $title = &mt('Course Authoring Space'); - } else { + if ($title eq '') { + $title = &mt('Course Authoring Space'); + } + } elsif ($title eq '') { $title = &mt('Authoring Space'); } - my ($target,$crumbtarget) = (' target="_top"','_top'); #FIXME lonpubdir: target="_parent" - if (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) { + 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'}; } my $output = @@ -6040,20 +6622,161 @@ sub CSTR_pageheader { } if ($crsauthor) { - $output .= ''.&Apache::lonmenu::constspaceform(); + $output .= ''.&Apache::lonmenu::constspaceform($frameset); } else { $output .= '
' #FIXME lonpubdir: &Apache::lonhtmlcommon::crumbs($uname.$thisdisfn.'/',$crumbtarget,'/priv','','+1',1)."
" .&Apache::lonhtmlcommon::select_recent('construct','recent','this.form.action=this.form.recent.value;this.form.submit()') .'' - .&Apache::lonmenu::constspaceform(); + .&Apache::lonmenu::constspaceform($frameset); } $output .= '
'; 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; +} + ############################################### ############################################### @@ -6115,6 +6838,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. @@ -6127,7 +6865,7 @@ other decorations will be returned. sub bodytag { my ($title,$function,$addentries,$bodyonly,$domain,$forcereg, $no_nav_bar,$bgcolor,$args,$advtoolsref,$ltiscope,$ltiuri, - $ltimenu,$menucoll,$menuref)=@_; + $ltimenu,$menucoll,$menuref,$showncrumbsref)=@_; my $public; if ((($env{'user.name'} eq 'public') && ($env{'user.domain'} eq 'public')) @@ -6262,11 +7000,40 @@ sub bodytag { # $titleinfo = &CSTR_pageheader(); #FIXME: Will be removed once all scripts have their own calls # } - $bodytag .= Apache::lonhtmlcommon::scripttag( - Apache::lonmenu::utilityfunctions($httphost), 'start'); + my $need_endlcint; + unless ($args->{'switchserver'}) { + $bodytag .= Apache::lonhtmlcommon::scripttag( + Apache::lonmenu::utilityfunctions($httphost), 'start'); + $need_endlcint = 1; + } + 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); + my ($left,$right) = Apache::lonmenu::primary_menu($crstype,$ltimenu,$menucoll,$menuref, + $args->{'links_disabled'}, + $args->{'links_target'}, + $collapsible); if ($env{'request.noversionuri'} =~ m{^/res/adm/pages/}) { if ($dc_info) { @@ -6274,6 +7041,9 @@ sub bodytag { } $bodytag .= qq|
$left $role
$realm $dc_info
|; + if ($need_endlcint) { + $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + } return $bodytag; } @@ -6291,6 +7061,9 @@ sub bodytag { #if directed to not display the secondary menu, don't. if ($args->{'no_secondary_menu'}) { + if ($need_endlcint) { + $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + } return $bodytag; } #don't show menus for public users @@ -6298,18 +7071,22 @@ sub bodytag { unless ($args->{'no_inline_menu'}) { $bodytag .= Apache::lonmenu::secondary_menu($httphost,$ltiscope,$ltimenu, $args->{'no_primary_menu'}, - $menucoll,$menuref); + $menucoll,$menuref, + $args->{'links_disabled'}, + $args->{'links_target'}); } $bodytag .= Apache::lonmenu::serverform(); - $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + if ($need_endlcint) { + $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 { $bodytag .= &Apache::lonmenu::prepare_functions($env{'request.noversionuri'}, @@ -6317,13 +7094,19 @@ sub bodytag { $args->{'bread_crumbs'}, $advtoolsref,'',$hostname); } - }else{ - # this is to seperate menu from content when there's no secondary - # menu. Especially needed for public accessible ressources. + } else { + # this is to separate menu from content when there's no secondary + # menu. Especially needed for publicly accessible resources. $bodytag .= '
'; - $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + if ($need_endlcint) { + $bodytag .= Apache::lonhtmlcommon::scripttag('', 'end'); + } + } + if ($args->{'collapsible_header'} ne '') { + $bodytag .= $args->{'collapsible_header'}. + '
'. + '
'; } - return $bodytag; } @@ -6393,12 +7176,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; } @@ -6472,6 +7288,7 @@ body { line-height:130%; font-size:0.83em; color:$font; + background-color: $pgbg_or_bgcolor; } a:focus, @@ -6483,6 +7300,14 @@ form, .inline { display: inline; } +.LC_menus_content.shown{ + display: block; +} + +.LC_menus_content.hidden { + display: none; +} + .LC_right { text-align:right; } @@ -6503,6 +7328,12 @@ form, .inline { width:400px; } +#LC_collapsible_separator { + border: 1px solid black; + width: 99.9%; + height: 0px; +} + .LC_iframecontainer { width: 98%; margin: 0; @@ -7715,6 +8546,11 @@ fieldset { /* overflow: hidden; */ } +fieldset#LC_selectuser { + margin: 0; + padding: 0; +} + article.geogebraweb div { margin: 0; } @@ -8258,6 +9094,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; } @@ -8375,6 +9215,18 @@ ul.LC_funclist li { cursor:pointer; } +.LCisDisabled { + cursor: not-allowed; + opacity: 0.5; +} + +a[aria-disabled="true"] { + color: currentColor; + display: inline-block; /* For IE11/ MS Edge bug */ + pointer-events: none; + text-decoration: none; +} + pre.LC_wordwrap { white-space: pre-wrap; white-space: -moz-pre-wrap; @@ -8548,7 +9400,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 @@ -8582,7 +9440,7 @@ sub headtag { $inhibitprint = &print_suppression(); } - if (!$args->{'frameset'}) { + if (!$args->{'frameset'} && !$args->{'switchserver'}) { $result .= &Apache::lonhtmlcommon::htmlareaheaders(); } if ($args->{'force_register'} && $env{'request.noversionuri'} !~ m{^/res/adm/pages/}) { @@ -8590,7 +9448,8 @@ sub headtag { } if (!$args->{'no_nav_bar'} && !$args->{'only_body'} - && !$args->{'frameset'}) { + && !$args->{'frameset'} + && !$args->{'switchserver'}) { $result .= &help_menu_js($httphost); $result.=&modal_window(); $result.=&togglebox_script(); @@ -8611,15 +9470,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'}; @@ -8756,8 +9645,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 .= ' /'; } @@ -8772,7 +9665,7 @@ OFFLOAD } if ($clientmobile) { $result .= ' - + '; } $result .= ''."\n"; @@ -8844,7 +9737,8 @@ 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,undef,1); + my $clientip = &Apache::lonnet::get_requestor_ip(); + my $blocked = &blocking_status('printout',$clientip,$cnum,$cdom,undef,1); if ($blocked) { my $checkrole = "cm./$cdom/$cnum"; if ($env{'request.course.sec'} ne '') { @@ -8955,6 +9849,11 @@ $args - additional optional args support no_auto_mt_title -> prevent &mt()ing the title arg bread_crumbs -> Array containing breadcrumbs bread_crumbs_component -> if exists show it as headline else show only the breadcrumbs + bread_crumbs_style -> breadcrumbs are contained within
, + and &standard_css() contains CSS for #LC_breadcrumbs, if you want + to override those values, or add to them, specify the value to + include in the style attribute to include in the div tag by using + bread_crumbs_style (e.g., overflow: visible) bread_crumbs_nomenu -> if true will pass false as the value of $menulink to lonhtmlcommon::breadcrumbs group -> includes the current group, if page is for a @@ -8963,6 +9862,10 @@ $args - additional optional args support will contain https:// if server uses https (as per hosts.tab), but request is for http hostname -> hostname, originally from $r->hostname(), (optional). + links_disabled -> Links in primary and secondary menus are disabled + (Can enable them once page has loaded - see lonroles.pm + for an example). + links_target -> Target for links, e.g., _parent (optional). =back @@ -9051,6 +9954,7 @@ sub start_page { } } + my $showncrumbs; if (! exists($args->{'skip_phases'}{'body'}) ) { if ($args->{'frameset'}) { my $attr_string = &make_attr_string($args->{'force_register'}, @@ -9063,7 +9967,8 @@ sub start_page { $args->{'only_body'}, $args->{'domain'}, $args->{'force_register'}, $args->{'no_nav_bar'}, $args->{'bgcolor'}, $args, - \@advtools,$ltiscope,$ltiuri,\%ltimenu,$menucoll,\%menu); + \@advtools,$ltiscope,$ltiuri,\%ltimenu,$menucoll, + \%menu,\$showncrumbs); } } @@ -9085,6 +9990,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') { @@ -9107,12 +10013,23 @@ 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); + $result .= &Apache::lonhtmlcommon::breadcrumbs($args->{'bread_crumbs_component'}, + '',$menulink,'', + $args->{'bread_crumbs_style'}); } else { - $result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink); + $result .= &Apache::lonhtmlcommon::breadcrumbs('','',$menulink,'', + $args->{'bread_crumbs_style'}); } + } } return $result; } @@ -9154,7 +10071,7 @@ sub menucoll_in_effect { if ($env{'request.course.id'}) { $menucoll = $env{'course.'.$env{'request.course.id'}.'.menudefault'}; if ($env{'request.deeplink.login'}) { - my ($deeplink_symb,$deeplink); + my ($deeplink_symb,$deeplink,$check_login_symb); my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; if ($env{'request.noversionuri'} =~ m{^/(res|uploaded)/}) { @@ -9164,11 +10081,21 @@ sub menucoll_in_effect { $deeplink = $navmap->get_mapparam(undef, &Apache::lonnet::declutter($env{'request.noversionuri'}), '0.deeplink'); + } else { + $check_login_symb = 1; } } else { - $deeplink = &Apache::lonnet::EXT('resource.0.deeplink'); + my $symb = &Apache::lonnet::symbread(); + if ($symb) { + $deeplink = &Apache::lonnet::EXT('resource.0.deeplink',$symb); + } else { + $check_login_symb = 1; + } } } else { + $check_login_symb = 1; + } + if ($check_login_symb) { $deeplink_symb = &deeplink_login_symb($cnum,$cdom); if ($deeplink_symb =~ /\.(page|sequence)$/) { my $mapname = &Apache::lonnet::deversion((&Apache::lonnet::decode_symb($deeplink_symb))[2]); @@ -9181,7 +10108,7 @@ sub menucoll_in_effect { } } if ($deeplink ne '') { - my ($state,$others,$listed,$scope,$protect,$display) = split(/,/,$deeplink); + my ($state,$others,$listed,$scope,$protect,$display,$target) = split(/,/,$deeplink); if ($display =~ /^\d+$/) { $deeplinkmenu = 1; $menucoll = $display; @@ -9236,6 +10163,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');