--- loncom/interface/lonmenu.pm 2015/04/14 21:35:52 1.431.2.2 +++ loncom/interface/lonmenu.pm 2021/11/30 15:55:37 1.514 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Routines to control the menu # -# $Id: lonmenu.pm,v 1.431.2.2 2015/04/14 21:35:52 musolffc Exp $ +# $Id: lonmenu.pm,v 1.514 2021/11/30 15:55:37 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -99,7 +99,7 @@ It gets filled in the BEGIN block of thi =over -=item prep_menuitems(\@menuitem) +=item prep_menuitems(\@menuitem,$ltitarget) This routine wraps a menuitem in proper HTML. It is used by primary_menu() and secondary_menu(). @@ -124,9 +124,13 @@ dropdown list when mouse hovers over top (no hover psuedo class) via LC_hoverable class for
  • tag for top- level item, which employs jQuery to handle behavior on mouseover. -Inputs: 4 - (a) link and (b) target for anchor href in top level item, - (c) title for text wrapped by anchor tag in top level item. - (d) reference to array of arrays of sub-menu items. +Inputs: 6 - (a) link and (b) target for anchor href in top level item, + (c) title for text wrapped by anchor tag in top level item, + (d) reference to array of arrays of sub-menu items, + (e) boolean to indicate whether to call &mt() to translate + name of menu item, + (f) optional class for
  • element in primary menu, for which + sub menu is being generated. The underlying datastructure used in (d) contains data from mydesk.tab. It consists of an array which has an array for each item appearing in @@ -206,16 +210,17 @@ use Apache::lonenc(); use Apache::lonlocal; use Apache::lonmsg(); use LONCAPA qw(:DEFAULT :match); +use LONCAPA::ltiutils; use HTML::Entities(); use Apache::lonwishlist(); use vars qw(@desklines %category_names %category_members %category_positions - $readdesk @primary_menu %primary_submenu @secondary_menu); + $readdesk @primary_menu %primary_submenu @secondary_menu %secondary_submenu); my @inlineremote; sub prep_menuitem { - my ($menuitem) = @_; + my ($menuitem,$ltitarget,$listclass,$linkattr) = @_; return '' unless(ref($menuitem) eq 'ARRAY'); my $link; if ($$menuitem[1]) { # graphical Link @@ -225,10 +230,14 @@ sub prep_menuitem { } else { # textual Link $link = &mt($$menuitem[3]); } - return '
  • ':'
  • ').'$link
  • |; + . qq| href="$$menuitem[0]"$target $linkattr>$link|; } # primary_menu() evaluates @primary_menu and returns a two item array, @@ -238,7 +247,8 @@ sub prep_menuitem { # @primary_menu is filled within the BEGIN block of this module with # entries from mydesk.tab sub primary_menu { - my (%menu); + my ($crstype,$ltimenu,$menucoll,$menuref,$links_disabled) = @_; + my (%menu,%ltiexc,%menuopts); # each element of @primary contains following array: # (link url, icon path, alt text, link text, condition, position) my $public; @@ -246,6 +256,30 @@ sub primary_menu { || (($env{'user.name'} eq '') && ($env{'user.domain'} eq ''))) { $public = 1; } + my $rolecount; + if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) { + my $update=$env{'user.update.time'}; + if (!$update) { + $update = $env{'user.login.time'}; + } + my %roles_in_env; + $rolecount = &Apache::lonroles::roles_from_env(\%roles_in_env,$update); + } + my ($lti,$ltitarget); + if ($env{'request.lti.login'}) { + $lti = 1; + $ltitarget = $env{'request.lti.target'}; + if (ref($ltimenu) eq 'HASH') { + foreach my $item ('fullname','logout') { + unless ($ltimenu->{$item}) { + $ltiexc{$item} = 1; + } + } + } + } + if (($menucoll) && (ref($menuref) eq 'HASH')) { + %menuopts = %{$menuref}; + } foreach my $menuitem (@primary_menu) { # evaluate conditions next if ref($menuitem) ne 'ARRAY'; # @@ -260,40 +294,96 @@ sub primary_menu { && !$public; # only visible to public # users next if $$menuitem[4] eq 'roles' ##show links depending on - && &Apache::loncommon::show_course(); ##term 'Courses' or - next if $$menuitem[4] eq 'courses' ##'Roles' wanted - && !&Apache::loncommon::show_course(); ## - + && (&Apache::loncommon::show_course() ##term 'Courses' or + || $lti); ##'Roles' wanted + next if $$menuitem[4] eq 'courses' ##and not LTI access + && (!&Apache::loncommon::show_course() + || $lti); + next if $$menuitem[4] eq 'notlti' + && $lti; + next if $$menuitem[4] eq 'ltiexc' + && exists($ltiexc{lc($menuitem->[3])}); my $title = $menuitem->[3]; + if (($crstype eq 'Placement') && (!$env{'request.role.adv'})) { + if ($menuitem->[4] eq 'courses') { + next unless ($rolecount>1); + } else { + next unless (($title eq 'Personal') || ($title eq 'Logout')); + } + } my $position = $menuitem->[5]; if ($position eq '') { $position = 'right'; } + if ($env{'request.course.id'} && $menucoll) { + if (($menuitem->[6]) && (!$menuopts{$menuitem->[6]})) { + if ($menuitem->[6] eq 'pers') { + if ($menuopts{'name'} && !$ltiexc{'fullname'} && + $env{'user.name'} && $env{'user.domain'}) { + $menu{$position} .= '
  • '. + &Apache::loncommon::plainname($env{'user.name'}, + $env{'user.domain'}).'
  • '; + next; + } else { + next; + } + } else { + next; + } + } + } + my ($listclass,$linkattr); + if ($links_disabled) { + $listclass = 'LCisDisabled'; + $linkattr = 'aria-disabled="true"'; + } if (defined($primary_submenu{$title})) { my ($link,$target); if ($menuitem->[0] ne '') { $link = $menuitem->[0]; - $target = '_top'; + unless ($ltitarget eq 'iframe') { + $target = '_top'; + } } else { $link = '#'; } my @primsub; if (ref($primary_submenu{$title}) eq 'ARRAY') { foreach my $item (@{$primary_submenu{$title}}) { + next if (($crstype eq 'Placement') && (!$env{'request.role.adv'})); next if (($item->[2] eq 'wishlist') && (!$env{'user.adv'})); next if ((($item->[2] eq 'portfolio') || ($item->[2] eq 'blog')) && (!&Apache::lonnet::usertools_access('','',$item->[2], undef,'tools'))); + if ($env{'request.course.id'} && $menucoll) { + next if ($item->[3]) && (!$menuopts{$item->[3]}); + } push(@primsub,$item); } + if ($title eq 'Personal') { + if ($env{'user.name'} && $env{'user.domain'} && !$ltiexc{'fullname'}) { + unless (($env{'request.course.id'}) && ($menucoll) && (!$menuopts{'name'})) { + $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'}); + } + } + next if (($env{'request.course.id'}) && ($menucoll) && ($title eq 'Personal') && + (!@primsub)); + if ($title eq 'Personal') { + $title = &mt($title); + } + } else { + $title = &mt($title); + } if (@primsub > 0) { - $menu{$position} .= &create_submenu($link,$target,$title,\@primsub,1); + $menu{$position} .= &create_submenu($link,$target,$title,\@primsub,1,undef,$listclass,$linkattr); } elsif ($link) { - $menu{$position} .= '
  • '.&mt($title).'
  • '; + $menu{$position} .= ($listclass?'
  • ':'
  • '). + ''.$title.'
  • '; } } } elsif ($$menuitem[3] eq 'Help') { # special treatment for helplink + next if ($crstype eq 'Placement'); if ($public) { my $origmail = $Apache::lonnet::perlvar{'lonSupportEMail'}; my $defdom = &Apache::lonnet::default_login_domain(); @@ -301,13 +391,22 @@ sub primary_menu { 'helpdeskmail', $defdom,$origmail); if ($to ne '') { - $menu{$position} .= &prep_menuitem($menuitem); + $menu{$position} .= &prep_menuitem($menuitem,$ltitarget,$listclass,$linkattr); } } else { - $menu{$position} .= '
  • '.&Apache::loncommon::top_nav_help('Help').'
  • '; + $menu{$position} .= ($listclass?'
  • ':'
  • '). + &Apache::loncommon::top_nav_help('Help',$linkattr). + '
  • '; } + } elsif ($$menuitem[3] eq 'Log In') { + if ($public) { + if (&Apache::lonnet::get_saml_landing()) { + $$menuitem[0] = '/adm/login'; + } + } + $menu{$position} .= prep_menuitem($menuitem,$ltitarget,$listclass,$linkattr); } else { - $menu{$position} .= prep_menuitem($menuitem); + $menu{$position} .= prep_menuitem($menuitem,$ltitarget,$listclass,$linkattr); } } my @output = ('',''); @@ -346,7 +445,7 @@ sub getauthor{ } sub secondary_menu { - my ($httphost) = @_; + my ($httphost,$ltiscope,$ltimenu,$noprimary,$menucoll,$menuref,$links_disabled) = @_; my $menu; my $crstype = &Apache::loncommon::course_type(); @@ -354,26 +453,29 @@ sub secondary_menu { ? "/$env{'request.course.sec'}" : ''); my $canedit = &Apache::lonnet::allowed('mdc', $env{'request.course.id'}); + my $canvieweditor = &Apache::lonnet::allowed('cev', $env{'request.course.id'}); my $canviewroster = $env{'course.'.$env{'request.course.id'}.'.student_classlist_view'}; if ($canviewroster eq 'disabled') { undef($canviewroster); } my $canviewgrps = &Apache::lonnet::allowed('vcg', $crs_sec); - my $canmodifyuser = &Apache::lonnet::allowed('cst', $crs_sec); + my $canmodifyuser = &Apache::lonnet::allowed('cst', $crs_sec); + my $canviewusers = &Apache::lonnet::allowed('vcl', $crs_sec); my $canviewwnew = &Apache::lonnet::allowed('whn', $crs_sec); + my $canviewpara = &Apache::lonnet::allowed('vpa', $crs_sec); my $canmodpara = &Apache::lonnet::allowed('opa', $crs_sec); my $canvgr = &Apache::lonnet::allowed('vgr', $crs_sec); my $canmgr = &Apache::lonnet::allowed('mgr', $crs_sec); + my $canplc = &Apache::lonnet::allowed('plc', $crs_sec); my $author = &getauthor(); - my ($cdom,$cnum,$showsyllabus,$showfeeds,$showresv); + my ($cdom,$cnum,$showsyllabus,$showfeeds,$showresv,$grouptools, + $lti,$ltimapres,%ltiexc,%menuopts); + $grouptools = 0; if ($env{'request.course.id'}) { $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; - if ($canedit) { - $showsyllabus = 1; - $showfeeds = 1; - } else { + unless ($canedit || $canvieweditor) { unless (&Apache::lonnet::is_on_map("public/$cdom/$cnum/syllabus")) { if (($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'}) || ($env{'course.'.$env{'request.course.id'}.'.uploadedsyllabus'}) || @@ -386,12 +488,44 @@ sub secondary_menu { $showfeeds = 1; } } - unless ($canmgr) { + unless ($canmgr || $canvgr) { my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom); if (keys(%slots) > 0) { $showresv = 1; } } + if ($env{'request.course.groups'} ne '') { + foreach my $group (split(/:/,$env{'request.course.groups'})) { + next unless ($group =~ /^\w+$/); + my @privs = split(/:/,$env{"user.priv.$env{'request.role'}./$cdom/$cnum/$group"}); + shift(@privs); + if (@privs) { + $grouptools ++; + } + } + } + if ($env{'request.lti.login'}) { + $lti = 1; + if (ref($ltimenu) eq 'HASH') { + foreach my $item ('fullname','coursetitle','role','logout','grades') { + unless ($ltimenu->{$item}) { + $ltiexc{$item} = 1; + } + } + } + if (($ltiscope eq 'map') || ($ltiscope eq 'resource')) { + $ltimapres = 1; + } + } + } + if (($menucoll) && (ref($menuref) eq 'HASH')) { + %menuopts = %{$menuref}; + } + + my ($listclass,$linkattr); + if ($links_disabled) { + $listclass = 'LCisDisabled'; + $linkattr = 'aria-disabled="true"'; } my ($canmodifycoauthor); @@ -403,71 +537,153 @@ sub secondary_menu { } } - my %groups = &Apache::lonnet::get_active_groups( - $env{'user.domain'}, $env{'user.name'}, - $env{'course.' . $env{'request.course.id'} . '.domain'}, - $env{'course.' . $env{'request.course.id'} . '.num'}); - - my ($roleswitcher_js,$roleswitcher_form); + my ($roleswitcher_js,$roleswitcher_form,$ltitarget); + if ($env{'request.lti.login'}) { + $ltitarget = $env{'request.lti.target'}; + } foreach my $menuitem (@secondary_menu) { # evaluate conditions next if ref($menuitem) ne 'ARRAY'; + next if (($crstype eq 'Placement') && ($$menuitem[3] ne 'Roles') && (!$env{'request.role.adv'})); next if $$menuitem[4] ne 'always' && ($$menuitem[4] ne 'author' && $$menuitem[4] ne 'cca') && !$env{'request.course.id'}; - next if $$menuitem[4] =~ /^mdc/ - && !$canedit; + next if $$menuitem[4] =~ /^crsedit/ + && (!$canedit && !$canvieweditor); next if $$menuitem[4] eq 'nvgr' - && $canvgr; + && ($canvgr || $ltiexc{'grades'}); next if $$menuitem[4] eq 'vgr' && !$canvgr; - next if $$menuitem[4] eq 'cst' - && !$canmodifyuser; - next if $$menuitem[4] eq 'ncst' - && ($canmodifyuser || !$canviewroster); + next if $$menuitem[4] eq 'viewusers' + && !$canmodifyuser && !$canviewusers; + next if $$menuitem[4] eq 'noviewusers' + && ($canmodifyuser || $canviewusers || !$canviewroster); next if $$menuitem[4] eq 'mgr' && !$canmgr; next if $$menuitem[4] eq 'showresv' && !$showresv; next if $$menuitem[4] eq 'whn' && !$canviewwnew; - next if $$menuitem[4] eq 'opa' - && !$canmodpara; - next if $$menuitem[4] =~ /showgroups$/ - && !$canviewgrps - && !%groups; + next if $$menuitem[4] eq 'params' + && (!$canmodpara && !$canviewpara); + next if $$menuitem[4] eq 'showgroups' + && ($canviewgrps || !$grouptools); next if $$menuitem[4] eq 'showsyllabus' && !$showsyllabus; next if $$menuitem[4] eq 'showfeeds' && !$showfeeds; + next if $$menuitem[4] eq 'plc' + && !$canplc; next if $$menuitem[4] eq 'author' && !$author; next if $$menuitem[4] eq 'cca' && !$canmodifycoauthor; + next if $$menuitem[4] eq 'notltimapres' + && $ltimapres; + next if $$menuitem[4] eq 'notlti' + && $lti; + next if $$menuitem[4] eq 'lti' + && (!$lti || !$noprimary); + next if $$menuitem[3] eq 'Logout' + && $ltiexc{'logout'}; - if ($$menuitem[3] eq 'Roles' && $env{'request.course.id'}) { + my $title = $menuitem->[3]; + if ($env{'request.course.id'} && $menucoll) { + unless ($$menuitem[5] eq 'roles') { + next if (($$menuitem[5]) && (!$menuopts{$$menuitem[5]})); + } + } + if (defined($secondary_submenu{$title})) { + my ($link,$target); + if ($menuitem->[0] ne '') { + $link = $menuitem->[0]; + unless ($ltitarget eq 'iframe') { + $target = '_top'; + } + } else { + $link = '#'; + } + my @scndsub; + if (ref($secondary_submenu{$title}) eq 'ARRAY') { + foreach my $item (@{$secondary_submenu{$title}}) { + if (ref($item) eq 'ARRAY') { + next if ($item->[2] eq 'vgr' && !$canvgr); + next if ($item->[2] eq 'opa' && !$canmodpara); + next if ($item->[2] eq 'vpa' && !$canviewpara); + next if ($item->[2] eq 'viewusers' && !($canmodifyuser || $canviewusers)); + next if ($item->[2] eq 'mgr' && !$canmgr); + next if ($item->[2] eq 'vcg' && !$canviewgrps); + next if ($item->[2] eq 'crsedit' && !$canedit && !$canvieweditor); + next if ($item->[2] eq 'params' && !$canmodpara && !$canviewpara); + next if ($item->[2] eq 'author' && !$author); + next if ($item->[2] eq 'cca' && !$canmodifycoauthor); + next if ($item->[2] eq 'lti' && !$lti); + if ($item->[2] =~ /^lti(portfolio|wishlist|blog)$/) { + my $tool = $1; + next if !$lti; + next if (!&Apache::lonnet::usertools_access('','',$tool, + undef,'tools')); + } + push(@scndsub,$item); + } + } + if ($title eq 'Personal' && $env{'user.name'} && $env{'user.domain'}) { + unless ($ltiexc{'fullname'}) { + $title = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'}); + } + } + if (@scndsub > 0) { + $menu .= &create_submenu($link,$target,&mt($title),\@scndsub,1,undef, + $listclass,$linkattr); + } elsif ($link ne '#') { + $menu .= ($listclass?'
  • ':'
  • '). + ''. + &mt($title).'
  • '; + } + } + } elsif ($$menuitem[3] eq 'Roles' && $env{'request.course.id'}) { # special treatment for role selector - ($roleswitcher_js,$roleswitcher_form,my $switcher) = + my ($switcher,$has_opa_priv); + ($roleswitcher_js,$roleswitcher_form,$switcher,$has_opa_priv) = &roles_selector( - $env{'course.' . $env{'request.course.id'} . '.domain'}, - $env{'course.' . $env{'request.course.id'} . '.num'}, - $httphost + $env{'course.' . $env{'request.course.id'} . '.domain'}, + $env{'course.' . $env{'request.course.id'} . '.num'}, + $httphost,$ltitarget ); + if (($$menuitem[5]) && (!$menuopts{$$menuitem[5]})) { + next unless ($has_opa_priv); + } $menu .= $switcher; + } elsif ($$menuitem[3] eq 'Help') { # special treatment for helplink + next if ($crstype eq 'Placement'); + $menu .= '
  • '.&Apache::loncommon::top_nav_help('Help').'
  • '; } else { if ($$menuitem[3] eq 'Syllabus' && $env{'request.course.id'}) { my $url = $$menuitem[0]; $url =~ s{\[cdom\]/\[cnum\]}{$cdom/$cnum}; if (&Apache::lonnet::is_on_map($url)) { - unless ($$menuitem[0] =~ /\?register=1/) { - $$menuitem[0] .= '?register=1'; + unless ($$menuitem[0] =~ /(\?|\&)register=1/) { + $$menuitem[0] .= (($$menuitem[0]=~/\?/)? '&' : '?').'register=1'; } } else { - $$menuitem[0] =~ s{\?register=1}{}; + $$menuitem[0] =~ s{\&?register=1}{}; + } + if ($env{'course.'.$env{'request.course.id'}.'.externalsyllabus'} =~ m{^http://}) { + if (($ENV{'SERVER_PORT'} == 443) || ($env{'request.use_absolute'} =~ m{^https://})) { + unless ((&Apache::lonnet::uses_sts()) || (&Apache::lonnet::waf_allssl())) { + unless ($$menuitem[0] =~ m{^https?://}) { + $$menuitem[0] = 'http://'.$ENV{'SERVER_NAME'}.$$menuitem[0]; + } + unless ($$menuitem[0] =~ /(\&|\?)usehttp=1/) { + $$menuitem[0] .= (($$menuitem[0]=~/\?/) ? '&' : '?').'usehttp=1'; + } + } + } } + $$menuitem[0] = &HTML::Entities::encode($$menuitem[0],'&<>"'); } - $menu .= &prep_menuitem(\@$menuitem); + $menu .= &prep_menuitem(\@$menuitem,$ltitarget,$listclass,$linkattr); } } if ($menu =~ /\[url\].*\[symb\]/) { @@ -484,13 +700,14 @@ sub secondary_menu { my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; ($escurl = $env{'request.filename'}) =~ s{^\Q$londocroot\E}{}; $escurl = &escape($escurl); - } + } $menu =~ s/\[url\]/$escurl/g; $menu =~ s/\[symb\]/$escsymb/g; } $menu =~ s/\[uname\]/$$author{user}/g; $menu =~ s/\[udom\]/$$author{dom}/g; - if ($showsyllabus || $showfeeds) { + $menu =~ s/\[javascript\]/javascript:/g; + if ($env{'request.course.id'}) { $menu =~ s/\[cnum\]/$cnum/g; $menu =~ s/\[cdom\]/$cdom/g; } @@ -504,32 +721,22 @@ sub secondary_menu { } sub create_submenu { - my ($link,$target,$title,$submenu,$translate) = @_; + my ($link,$target,$title,$submenu,$translate,$addclass,$listclass,$linkattr) = @_; return unless (ref($submenu) eq 'ARRAY'); my $disptarget; if ($target ne '') { $disptarget = ' target="'.$target.'"'; } - my $name; - if ($title eq 'Personal') { - if ($env{'user.name'} && $env{'user.domain'}) { - $name = &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'}); - } else { - $name = &mt($title); - } - } else { - $name = &mt($title); - } - my $menu = '
  • '. + my $menu = '
  • '. ''. - ''.$name. + ''.$title. ''. ' ▼'. '
  • '; return $menu; @@ -539,8 +746,8 @@ sub create_submenu { # build the dropdown (and nested submenus) recursively # see perldoc create_submenu documentation for further information sub build_submenu { - my ($target, $submenu, $translate, $first_level) = @_; - if (!defined(@{$submenu})) { + my ($target, $submenu, $translate, $first_level, $listclass, $linkattr) = @_; + unless (@{$submenu}) { return ''; } @@ -583,13 +790,31 @@ sub build_submenu { next unless (($env{'user.name'} ne '') && ($env{'user.domain'} ne '')); $href =~ s/\[domain\]/$env{'user.domain'}/g; $href =~ s/\[user\]/$env{'user.name'}/g; + } elsif (($href =~ m{^/adm/preferences\?}) && ($href =~ /\[returnurl\]/)) { + my $returnurl = $ENV{'REQUEST_URI'}; + if ($ENV{'REQUEST_URI'} =~ m{/adm/preferences\?action=(?:changedomcoord|authorsettings)\&returnurl=([^\&]+)$}) { + $returnurl = $1; + } + if (($returnurl =~ m{^/adm/createuser($|\?action=)}) || + ($returnurl =~ m{^/priv/$match_domain/$match_username}) || + ($returnurl =~ m{^/res(/?$|/$match_domain/$match_username)})) { + $returnurl =~ s{\?.*$}{}; + $returnurl = '&returnurl='.&HTML::Entities::encode($returnurl,'"<>&\''); + } else { + undef($returnurl); + } + $href =~ s/\[returnurl\]/$returnurl/; } unless (($href eq '') || ($href =~ /^\#/)) { - $target = ' target="_top"'; + if ($target eq '_top') { + $target = ' target="_top"'; + } } - $menu .= '
  • '; - $menu .= '' . $title . ''; + $menu .= '
  • '; + $menu .= '' . $title . ''; $menu .= '
  • '; } } @@ -598,7 +823,7 @@ sub build_submenu { } sub innerregister { - my ($forcereg,$bread_crumbs,$group) = @_; + my ($forcereg,$bread_crumbs,$group,$pagebuttonshide,$hostname,$ltiscope,$ltiuri) = @_; my $const_space = ($env{'request.state'} eq 'construct'); my $is_const_dir = 0; @@ -608,43 +833,80 @@ sub innerregister { undef(@inlineremote); - my ($mapurl,$resurl); + my ($mapurl,$resurl,$crstype,$navmap); if ($env{'request.course.id'}) { +# +#course_type: Course, Community, or Placement +# + $crstype = &Apache::loncommon::course_type(); if ($env{'request.symb'}) { - ($mapurl, my $rid, $resurl) = &Apache::lonnet::decode_symb(&Apache::lonnet::symbread()); + my $ignorenull; + unless ($env{'request.noversionuri'} eq '/adm/navmaps') { + $ignorenull = 1; + } + my $symb = &Apache::lonnet::symbread('','',$ignorenull); + ($mapurl, my $rid, $resurl) = &Apache::lonnet::decode_symb($symb); my $coursetitle = $env{'course.'.$env{'request.course.id'}.'.description'}; my $maptitle = &Apache::lonnet::gettitle($mapurl); - my $restitle = &Apache::lonnet::gettitle(&Apache::lonnet::symbread()); - -#SD -#course_type only Course and Community? -# - my @crumbs; + my $restitle = &Apache::lonnet::gettitle($symb); + my (@crumbs,@mapcrumbs); + if (($env{'request.noversionuri'} ne '/adm/navmaps') && ($mapurl ne '') && + (!(($crstype eq 'Placement') && !$env{'request.role.adv'}))) { + unless ($ltiscope eq 'resource') { + if (($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'}) && + !(($ltiscope eq 'map') && (&Apache::lonnet::clutter($resurl) eq $ltiuri))) { + $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + @mapcrumbs = $navmap->recursed_crumbs($mapurl,$restitle); + } + } + } + } unless (($forcereg) && ($env{'request.noversionuri'} eq '/adm/navmaps') && - ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'})) { - @crumbs = ({text => Apache::loncommon::course_type() - . ' Contents', + ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) || + (($crstype eq 'Placement') && (!$env{'request.role.adv'})) || + ($ltiscope eq 'map') || ($ltiscope eq 'resource')) { + @crumbs = ({text => $crstype.' Contents', href => "Javascript:gopost('/adm/navmaps','')"}); } if ($mapurl ne $env{'course.'.$env{'request.course.id'}.'.url'}) { - push(@crumbs, {text => '...', - no_mt => 1}); + if (@mapcrumbs) { + push(@crumbs,@mapcrumbs); + } elsif (!(($crstype eq 'Placement') && (!$env{'request.role.adv'})) && + ($ltiscope ne 'map') && ($ltiscope ne 'resource')) { + push(@crumbs, {text => '...', + no_mt => 1}); + } } - push @crumbs, {text => $maptitle, no_mt => 1} if ($maptitle - && $maptitle ne 'default.sequence' - && $maptitle ne $coursetitle); - - push @crumbs, {text => $restitle, no_mt => 1} if $restitle; + unless ((($crstype eq 'Placement') && (!$env{'request.role.adv'})) || (@mapcrumbs) || + (!$maptitle) || ($maptitle eq 'default.sequence') || + ($mapurl eq $env{'course.'.$env{'request.course.id'}.'.url'}) || + ($ltiscope eq 'resource')) { + push @crumbs, {text => $maptitle, no_mt => 1, + href => &Apache::lonnet::clutter($mapurl).'?navmap=1'}; + } + if ($restitle && !@mapcrumbs) { + push(@crumbs,{text => $restitle, no_mt => 1}); + } + my @tools; + if ($env{'request.filename'} =~ /\.page$/) { + my %breadcrumb_tools = &Apache::lonhtmlcommon::current_breadcrumb_tools(); + if (ref($breadcrumb_tools{'tools'}) eq 'ARRAY') { + @tools = @{$breadcrumb_tools{'tools'}}; + } + } &Apache::lonhtmlcommon::clear_breadcrumbs(); &Apache::lonhtmlcommon::add_breadcrumb(@crumbs); + if (@tools) { + &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',@tools); + } } else { $resurl = $env{'request.noversionuri'}; my $courseurl = &Apache::lonnet::courseid_to_courseurl($env{'request.course.id'}); - my $crstype = &Apache::loncommon::course_type(); my $title = &mt('View Resource'); if ($resurl =~ m{^\Q/uploaded$courseurl/supplemental/\E(default|\d+)/}) { &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['folderpath','title']); @@ -654,7 +916,7 @@ sub innerregister { } my $trail; if ($env{'form.folderpath'}) { - &prepare_functions($resurl,$forcereg,$group,undef,undef,1); + &prepare_functions($resurl,$forcereg,$group,undef,undef,1,$hostname); ($trail) = &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); } else { @@ -669,10 +931,10 @@ sub innerregister { } elsif ($resurl =~ m{^\Q/uploaded$courseurl/portfolio/syllabus/}) { &Apache::lonhtmlcommon::clear_breadcrumbs(); &prepare_functions('/public'.$courseurl."/syllabus", - $forcereg,$group,undef,undef,1); + $forcereg,$group,undef,undef,1,$hostname); $title = &mt('Syllabus File'); my ($trail) = - &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1); + &Apache::lonhtmlcommon::docs_breadcrumbs(undef,$crstype,undef,$title,1,$hostname); return $trail; } unless ($env{'request.state'} eq 'construct') { @@ -696,7 +958,7 @@ sub innerregister { $forceview,$editbutton); if (($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) || ($env{'request.role'} !~/^(aa|ca|au)/)) { - $editbutton = &prepare_functions($resurl,$forcereg,$group); + $editbutton = &prepare_functions($resurl,$forcereg,$group,'','','',$hostname); } if ($editbutton eq '') { $editbutton = &clear(6,1); @@ -709,12 +971,19 @@ sub innerregister { $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; $perms{'mdc'} = &Apache::lonnet::allowed('mdc',$env{'request.course.id'}); + $perms{'cev'} = &Apache::lonnet::allowed('cev',$env{'request.course.id'}); my @privs; + my $gradable_exttool; if ($env{'request.symb'} ne '') { - if ($env{'request.filename'}=~/$LONCAPA::assess_re/) { + if ($env{'request.noversionuri'} =~ m{^/adm/$cdom/$cnum/(\d+)/ext\.tool$}) { + if (&Apache::lonnet::EXT('resource.0.gradable') =~ /^yes$/i) { + $gradable_exttool = 1; + push(@privs,('mgr','vgr')); + } + } elsif ($env{'request.filename'}=~/$LONCAPA::assess_re/) { push(@privs,('mgr','vgr')); } - push(@privs,'opa'); + push(@privs,('opa','vpa')); } foreach my $priv (@privs) { $perms{$priv} = &Apache::lonnet::allowed($priv,$env{'request.course.id'}); @@ -726,8 +995,8 @@ sub innerregister { # # Determine whether or not to show Grades and Submissions buttons # - if ($env{'request.symb'} ne '' && - $env{'request.filename'}=~/$LONCAPA::assess_re/) { + if (($env{'request.symb'} ne '') && + (($env{'request.filename'}=~/$LONCAPA::assess_re/) || ($gradable_exttool))) { if ($perms{'mgr'}) { &switch('','',7,2,'pgrd.png','Content Grades','grades[_4]', "gocmd('/adm/grades','gradingmenu')", @@ -738,7 +1007,7 @@ sub innerregister { 'Content Submissions'); } } - if (($env{'request.symb'} ne '') && ($perms{'opa'})) { + if (($env{'request.symb'} ne '') && (($perms{'opa'}) || ($perms{'vpa'}))) { &switch('','',7,3,'pparm.png','Content Settings','parms[_2]', "gocmd('/adm/parmset','set')", 'Content Settings'); @@ -748,7 +1017,7 @@ sub innerregister { # # This applies to items inside a folder/page modifiable in the course. # - if (($env{'request.symb'}=~/^uploaded/) && ($perms{'mdc'})) { + if (($env{'request.symb'}=~/^uploaded/) && (($perms{'mdc'}) || ($perms{'cev'}))) { my $text = 'Edit Folder'; if (($mapurl =~ /\.page$/) || ($env{'request.symb'}=~ @@ -776,6 +1045,13 @@ sub innerregister { my $currdir = '/priv/'.$udom.'/'.$uname.'/'.$thisdisfn; if ($currdir =~ m-/$-) { $is_const_dir = 1; + if ($thisdisfn eq '') { + unless (($env{'request.course.id'}) && + ($env{'course.'.$env{'request.course.id'}.'.num'} eq $uname) && + ($env{'course.'.$env{'request.course.id'}.'.domain'} eq $udom)) { + $is_const_dir = 2; + } + } } else { $currdir =~ s|[^/]+$||; my $cleandisfn = &Apache::loncommon::escape_single($thisdisfn); @@ -803,10 +1079,58 @@ ENDMENUITEMS # We are in a course and looking at a registered URL # Should probably be in mydesk.tab # - $menuitems=(<new(); + if (ref($navmap)) { + my $mapres = $navmap->getResourceByUrl($ltiuri); + if (ref($mapres)) { + if ($navmap->isLastResource($mapres,$env{'request.symb'})) { + $showforw = 0; + } + if ($navmap->isFirstResource($mapres,$env{'request.symb'})) { + $showback = 0; + } + } + } + if ($showback) { + $menuitems.=" +s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1"; + } + if ($showforw) { + $menuitems.=" +s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3"; + } + } elsif (($crstype ne 'Placement') || ($env{'request.role.adv'})) { + $menuitems.=" s&2&1&back.png&&&gopost('/adm/flip','back:'+currentURL)&Previous content resource&&1 -s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3 +s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3"; + } else { +# Suppress display of backward arrow for Placement Tests +# Suppress display of forward arrow for Placement Tests if this is the last resource. + my $showforw = 1; + if ($env{'request.symb'}) { + my $navmap = Apache::lonnavmaps::navmap->new(); + if (ref($navmap)) { + if (&Apache::lonplacementtest::is_lastres($env{'request.symb'},$navmap)) { + $showforw = 0; + } + } + } + if ($showforw) { + $menuitems.=" +s&2&3&forw.png&&&gopost('/adm/flip','forward:'+currentURL)&Next content resource&&3"; + } + } + $menuitems .= (<[0]; + } + $buttonshide = $pagebuttonshide; + } else { + $countdown = &countdown_timer(); + $buttonshide = &hidden_button_check(); + } &Apache::lonhtmlcommon::clear_breadcrumb_tools(); &Apache::lonhtmlcommon::add_breadcrumb_tool( 'navigation', @inlineremote[21,23]); - my $countdown = &countdown_timer(); - if (&hidden_button_check() eq 'yes') { + if ($buttonshide eq 'yes') { if ($countdown) { &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$countdown); } + if ($showprogress) { + &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$showprogress); + } } else { my @tools = @inlineremote[93,91,81,82,83]; if ($countdown) { @@ -926,20 +1273,33 @@ ENDMENUITEMS } &advtools_crumbs(@inlineremote); } + } else { + if ($showprogress) { + &Apache::lonhtmlcommon::add_breadcrumb_tool('tools',$showprogress); + } + } + my ($topic_help,$topic_help_text); + if ($is_const_dir == 2) { + if ((($ENV{'SERVER_PORT'} == 443) || + ($Apache::lonnet::protocol{$Apache::lonnet::perlvar{'lonHostID'}} eq 'https')) && + (&Apache::lonnet::usertools_access($env{'user.name'},$env{'user.domain'},'webdav'))) { + $topic_help = 'Authoring_WebDAV,Authoring_WebDAV_Mac_10v6,Authoring_WebDAV_Mac_10v10,'. + 'Authoring_WebDAV_Windows_v7,Authoring_WebDAV_Linux_Centos'; + $topic_help_text = 'About WebDAV access'; + } } - return &Apache::lonhtmlcommon::scripttag('', 'start') - . &Apache::lonhtmlcommon::breadcrumbs(undef,undef,0) + . &Apache::lonhtmlcommon::breadcrumbs(undef,undef,0,'','','','',$topic_help,$topic_help_text) . &Apache::lonhtmlcommon::scripttag('', 'end'); } sub get_editbutton { - my ($cfile,$home,$switchserver,$forceedit,$forceview,$forcereg) = @_; + my ($cfile,$home,$switchserver,$forceedit,$forceview,$forcereg,$hostname) = @_; my $jscall; if (($forceview) && ($env{'form.todocs'})) { - my ($folderpath,$command); + my ($folderpath,$command,$navmap); if ($env{'request.symb'}) { - $folderpath = &Apache::loncommon::symb_to_docspath($env{'request.symb'}); + $folderpath = &Apache::loncommon::symb_to_docspath($env{'request.symb'},\$navmap); } elsif ($env{'form.folderpath'} =~ /^supplemental/) { $folderpath = $env{'form.folderpath'}; $command = '&forcesupplement=1'; @@ -947,11 +1307,16 @@ sub get_editbutton { $folderpath = &escape(&HTML::Entities::encode(&escape($folderpath),'<>&"')); $jscall = "go('/adm/coursedocs?folderpath=$folderpath$command')"; } else { + my $suppanchor; + if ($env{'form.folderpath'}) { + $suppanchor = $env{'form.anchor'}; + } $jscall = &Apache::lonhtmlcommon::jump_to_editres($cfile,$home,$switchserver, $forceedit,$forcereg,$env{'request.symb'}, &escape($env{'form.folderpath'}), - &escape($env{'form.title'}),$env{'form.idx'}, - &escape($env{'form.suppurl'},$env{'form.todocs'})); + &escape($env{'form.title'}),$hostname, + $env{'form.idx'},&escape($env{'form.suppurl'}), + $env{'form.todocs'},$suppanchor); } if ($jscall) { my $icon = 'pcstr.png'; @@ -968,7 +1333,7 @@ sub get_editbutton { } sub prepare_functions { - my ($resurl,$forcereg,$group,$bread_crumbs,$advtools,$docscrumbs) = @_; + my ($resurl,$forcereg,$group,$bread_crumbs,$advtools,$docscrumbs,$hostname) = @_; unless ($env{'request.registered'}) { undef(@inlineremote); } @@ -982,15 +1347,18 @@ sub prepare_functions { } my $editbutton = ''; + my $viewsrcbutton = ''; + my $clientip = &Apache::lonnet::get_requestor_ip(); # -# Determine whether or not to display 'Edit' icon/button +# Determine whether or not to display 'Edit' or 'View Source' icon/button # if ($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) { + my $blocked = &Apache::loncommon::blocking_status('about',$clientip,$2,$1); my $file=&Apache::lonnet::declutter($env{'request.filename'}); ($cfile,$home,$switchserver,$forceedit,$forceview) = &Apache::lonnet::can_edit_resource($file,$cnum,$cdom, &Apache::lonnet::clutter($resurl),$env{'request.symb'},$group); - if (($cfile) && ($home ne '') && ($home ne 'no_host')) { + if (($cfile) && ($home ne '') && ($home ne 'no_host') && (!$blocked)) { $editbutton = &get_editbutton($cfile,$home,$switchserver, $forceedit,$forceview,$forcereg); } @@ -1014,17 +1382,31 @@ sub prepare_functions { # This applies in course context # if (($perms{'mdc'}) && - (($resurl eq "/public/$cdom/$cnum/syllabus") || - ($resurl =~ m{^/uploaded/$cdom/$cnum/portfolio/syllabus/}))) { - $cfile = $resurl; + (($resurl =~ m{^/?public/$cdom/$cnum/syllabus}) || + ($resurl =~ m{^/?uploaded/$cdom/$cnum/portfolio/syllabus/}) || + (($resurl =~ m{^/?uploaded/$cdom/$cnum/default_\d+\.sequence$}) && ($env{'form.navmap'})))) { + if ($resurl =~ m{^/}) { + $cfile = $resurl; + } else { + $cfile = "/$resurl"; + } $home = &Apache::lonnet::homeserver($cnum,$cdom); if ($env{'form.forceedit'}) { $forceview = 1; } else { $forceedit = 1; } - $editbutton = &get_editbutton($cfile,$home,$switchserver, - $forceedit,$forceview,$forcereg); + if ($cfile =~ m{^/uploaded/$cdom/$cnum/default_\d+\.sequence$}) { + my $text = 'Edit Folder'; + &switch('','',7,4,'docs-22x22.png','Edit Folder','parms[_2]', + "gocmd('/adm/coursedocs','direct')", + 'Folder/Page Content'); + $editbutton = 1; + } else { + $editbutton = &get_editbutton($cfile,$home,$switchserver, + $forceedit,$forceview,$forcereg, + $hostname); + } } elsif (($resurl eq '/adm/extresedit') && (($env{'form.symb'}) || ($env{'form.folderpath'}))) { ($cfile,$home,$switchserver,$forceedit,$forceview) = @@ -1032,8 +1414,7 @@ sub prepare_functions { $env{'form.symb'}); if ($cfile ne '') { $editbutton = &get_editbutton($cfile,$home,$switchserver, - $forceedit,$forceview,$forcereg, - $env{'form.title'},$env{'form.suppurl'}); + $forceedit,$forceview,$forcereg); } } elsif (($resurl =~ m{^/?adm/viewclasslist$}) && (&Apache::lonnet::allowed('opa',$env{'request.course.id'}))) { @@ -1051,7 +1432,34 @@ sub prepare_functions { &Apache::lonnet::clutter($resurl),$env{'request.symb'},$group); if ($cfile ne '') { $editbutton = &get_editbutton($cfile,$home,$switchserver, - $forceedit,$forceview,$forcereg); + $forceedit,$forceview,$forcereg, + $hostname); + } + if ((($cfile eq '') || (!$editbutton)) && + ($resurl =~ /$LONCAPA::assess_re/)) { + my $showurl = &Apache::lonnet::clutter($resurl); + my $crs_sec = $env{'request.course.id'} . (($env{'request.course.sec'} ne '') + ? "/$env{'request.course.sec'}" + : ''); + if ((&Apache::lonnet::allowed('cre','/')) && + (&Apache::lonnet::metadata($resurl,'sourceavail') eq 'open')) { + $viewsrcbutton = 1; + } elsif (&Apache::lonnet::allowed('vxc',$crs_sec)) { + if ($showurl =~ m{^\Q/res/$cdom/\E($match_username)/}) { + my $auname = $1; + if (($env{'request.course.adhocsrcaccess'} ne '') && + (grep(/^\Q$auname\E$/,split(/,/,$env{'request.course.adhocsrcaccess'})))) { + $viewsrcbutton = 1; + } elsif ((&Apache::lonnet::metadata($resurl,'sourceavail') eq 'open') && + (&Apache::lonnet::allowed('bre',$crs_sec))) { + $viewsrcbutton = 1; + } + } + } + if ($viewsrcbutton) { + &switch('','',6,1,'pcstr.png','View Source','resource[_2]','open_source()', + 'View source code'); + } } } } @@ -1063,10 +1471,13 @@ sub prepare_functions { if ($resurl =~ m{^/?adm/($match_domain)/($match_username)/aboutme$}) { my ($sdom,$sname) = ($1,$2); unless (&Apache::lonnet::is_course($sdom,$sname)) { - &switch('','',6,4,'mail-message-new-22x22.png','Message to user', - '', - "go('/adm/email?compose=individual&recname=$sname&recdom=$sdom')", - 'Send message to specific user'); + my $blocked = &Apache::loncommon::blocking_status('about',$clientip,$sname,$sdom); + unless ($blocked) { + &switch('','',6,4,'mail-message-new-22x22.png','Message to user', + '', + "go('/adm/email?compose=individual&recname=$sname&recdom=$sdom')", + 'Send message to specific user'); + } } my $hideprivileged = 1; if (&Apache::lonnet::in_course($sdom,$sname,$cdom,$cnum,undef, @@ -1101,16 +1512,25 @@ sub prepare_functions { if (($env{'form.folderpath'} =~ /^supplemental/) && (&Apache::lonnet::allowed('mdc',$env{'request.course.id'})) && (($resurl =~ m{^/adm/wrapper/ext/}) || + ($resurl =~ m{^/adm/$cdom/$cnum/\d+/ext\.tool$}) || ($resurl =~ m{^/uploaded/$cdom/$cnum/supplemental/}) || ($resurl eq '/adm/supplemental') || ($resurl =~ m{^/public/$cdom/$cnum/syllabus$}) || ($resurl =~ m{^/adm/$match_domain/$match_username/aboutme$}))) { my @folders=split('&',$env{'form.folderpath'}); if ((@folders > 2) || ($resurl ne '/adm/supplemental')) { + my $suppanchor; + if ($resurl =~ m{^/adm/wrapper/ext/}) { + $suppanchor = $env{'form.anchor'}; + } my $esc_path=&escape(&HTML::Entities::encode(&escape($env{'form.folderpath'}),'<>&"')); + my $link = '/adm/coursedocs?command=direct&forcesupplement=1&supppath='. + "$esc_path&anchor=$suppanchor"; + if ($env{'request.use_absolute'} ne '') { + $link = $env{'request.use_absolute'}.$link; + } &switch('','',7,4,'docs-22x22.png','Edit Folder','parms[_2]', - "location.href='/adm/coursedocs?command=direct&forcesupplement=1&supppath=$esc_path'", - 'Folder/Page Content'); + "location.href='$link'",'Folder/Page Content'); } } } @@ -1121,7 +1541,7 @@ sub prepare_functions { &advtools_crumbs(@inlineremote); return $editbutton; } elsif ($env{'request.registered'}) { - return $editbutton; + return $editbutton || $viewsrcbutton; } else { if (ref($bread_crumbs) eq 'ARRAY') { if (@inlineremote > 0) { @@ -1211,7 +1631,8 @@ sub switch { unless ($env{'request.state'} eq 'construct') { push(@tools,63); } - if (($env{'environment.icons'} eq 'iconsonly') && + if ((($env{'environment.icons'} eq 'iconsonly') || + ($env{'environment.icons'} eq '') && ($env{'request.lti.login'})) && (grep(/^$idx$/,@tools))) { $inlineremote[$idx] = ''.$pic.''; @@ -1470,7 +1891,7 @@ sub rawconfig { sub check_for_rcrs { my $showreqcrs = 0; - my @reqtypes = ('official','unofficial','community','textbook'); + my @reqtypes = ('official','unofficial','community','textbook','placement'); foreach my $type (@reqtypes) { if (&Apache::lonnet::usertools_access($env{'user.name'}, $env{'user.domain'}, @@ -1536,56 +1957,234 @@ function toggleCountdown() { END } -# This creates a "done button" for timed events. The confirmation box is a jQuerey -# dialog widget. Clicking OK will set (done = 'true') which is checked in -# lonhomework.pm. +# This creates a "done button" for timed events. The confirmation box is a jQuery +# dialog widget. If the interval parameter requires a proctor key for the timed +# event to be marked done, there will also be a textbox where that can be entered. +# Clicking OK will set the value of LC_interval_done to 'true', and, if needed will +# set the value of LC_interval_done_proctorpass to the text entered in that box, +# and submit the corresponding form. +# +# The &zero_time() routine in lonhomework.pm is called when a page is rendered if +# LC_interval_done is true. +# sub done_button_js { - return < 'WARNING!', + preamble => 'You are trying to end this timed event early.', + map => 'Confirming that you are done will cause the time to expire and prevent you from changing any answers in the current folder.', + resource => 'Confirming that you are done will cause the time to expire for this question, and prevent you from changing your answer(s).', + okdone => 'Click "OK" if you are completely finished.', + cancel => 'Click "Cancel" to continue working.', + proctor => 'Ask a proctor to enter the key, then click "OK" if you are completely finished.', + ok => 'OK', + exit => 'Cancel', + key => 'Key:', + nokey => 'A proctor key is required', + ); + my $shownsymb = &HTML::Entities::encode(&Apache::lonenc::check_encrypt($env{'request.symb'})); + my $navmap = Apache::lonnavmaps::navmap->new(); + my ($missing,$tried) = (0,0); + if (ref($navmap)) { + my @resources=(); + if ($type eq 'map') { + my ($mapurl,$rid,$resurl)=&Apache::lonnet::decode_symb($env{'request.symb'}); + if ($env{'request.symb'} =~ /\.page$/) { + @resources=$navmap->retrieveResources($resurl,sub { $_[0]->is_problem() }); + } else { + @resources=$navmap->retrieveResources($mapurl,sub { $_[0]->is_problem() }); + } + } else { + my $res = $navmap->getBySymb($env{'request.symb'}); + if (ref($res)) { + if ($res->is_problem()) { + push(@resources,$res); + } + } + } + foreach my $res (@resources) { + if (ref($res->parts()) eq 'ARRAY') { + foreach my $part (@{$res->parts()}) { + if (!$res->tries($part)) { + $missing++; + } else { + $tried++; + } + } + } + } + } + if ($missing) { + $lt{'miss'} .= '

    '; + if ($type eq 'map') { + $lt{'miss'} .= &mt('Submissions are missing for [quant,_1,question part,question parts] in this folder.',$missing); + } else { + $lt{'miss'} .= &mt('Submissions are missing for [quant,_1,part] in this question.',$missing); + } + if ($missing > 1) { + $lt{'miss'} .= ' '.&mt('If you confirm you are done you will be unable to submit answers for them.').''; + } else { + $lt{'miss'} .= ' '.&mt('If you confirm you are done you will be unable to submit an answer for it.').'

    '; + } + } + $donebuttontext = &HTML::Entities::encode($donebuttontext,'<>&"'); + if ($proctor) { + if ($height !~ /^\d+$/) { + $height = 400; + if ($missing) { + $height += 60; + } + } + if ($width !~ /^\d+$/) { + $width = 400; + if ($missing) { + $width += 60; + } + } + return < + + + + + + +
    +

    $lt{'preamble'} $lt{$type}

    + $lt{'miss'} +

    $lt{'proctor'}

    +
    + + +
    +

    $lt{'cancel'}

    +
    + + + +END + } else { + if ($height !~ /^\d+$/) { + $height = 320; + if ($missing) { + $height += 60; + } + } + if ($width !~ /^\d+$/) { + $width = 320; + if ($missing) { + $width += 60; + } + } + if ($missing) { + $lt{'miss'} = '

    '.$lt{'miss'}.'

    '; + } + return < - - - +

    + + + +
    -
    -

    You are trying to end this timed event early. Confirming that you are done will cause the time to expire and prevent you from changing any answers in the current folder. Click "OK" if you are completely finished. Click "Cancel" to continue working.

    +
    +

    $lt{'preamble'} $lt{$type} $lt{'miss'} $lt{'okdone'} $lt{'cancel'}

    - END + } } sub utilityfunctions { my ($httphost) = @_; my $currenturl=&Apache::lonnet::clutter(&Apache::lonnet::fixversion((split(/\?/,$env{'request.noversionuri'}))[0])); - if ($currenturl =~ m{^/adm/wrapper/ext/} - && $env{'request.external.querystring'} ) { + my $currentsymb=&Apache::lonenc::check_encrypt($env{'request.symb'}); + if ($currenturl =~ m{^/adm/wrapper/ext/}) { + if ($env{'request.external.querystring'}) { $currenturl .= ($currenturl=~/\?/)?'&':'?'.$env{'request.external.querystring'}; + } + my ($anchor) = ($env{'request.symb'} =~ /(\#[^\#]+)$/); + if (($anchor) && ($currenturl !~ /\Q$anchor\E$/)) { + $currenturl .= $1; + } } $currenturl=&Apache::lonenc::check_encrypt(&unescape($currenturl)); - - my $currentsymb=&Apache::lonenc::check_encrypt($env{'request.symb'}); my $dc_popup_cid; if ($env{'user.adv'} && exists($env{'user.role.dc./'. @@ -1612,6 +2211,15 @@ sub utilityfunctions { my $countdown = &countdown_toggle_js(); + my $ltitarget; + if ($env{'request.lti.login'}) { + $ltitarget = $env{'request.lti.target'}; + } + + my $annotateurl = '/adm/annotation'; + if ($httphost) { + $annotateurl = '/adm/annotations'; + } my $hostvar = ' function setLCHost() { var lcHostname=""; @@ -1708,17 +2316,22 @@ function golist(url) { currentURL = null; currentSymb= null; var lcHostname = setLCHost(); - top.location.href=lcHostname+url; + var ltitarget = '$ltitarget'; + if (ltitarget == 'iframe') { + document.location.href=lcHostname+url; + } else { + top.location.href=lcHostname+url; + } } } -function catalog_info(isMobile) { +function catalog_info(url,isMobile) { if (isMobile == 1) { - openMyModal(window.location.pathname+'.meta?modal=1',500,400,'yes'); + openMyModal(url+'.meta?modal=1',500,400,'yes'); } else { - loncatinfo=window.open(window.location.pathname+'.meta',"LONcatInfo",'height=500,width=400,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no'); + loncatinfo=window.open(url+'.meta',"LONcatInfo",'height=500,width=400,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no'); } } @@ -1740,7 +2353,7 @@ function annotate() { annotator.document.write( '$start_page_annotate' +"
    " + +"action='$annotateurl'>" +"" +"<\\/form>" +'$end_page_annotate'); @@ -1761,6 +2374,23 @@ function open_StoredLinks_Import(rat) { newWin.focus(); } +function open_source() { + sourcewin=window.open('/adm/source?inhibitmenu=yes&viewonly=1&filename='+currentURL,'LONsource', + 'height=500,width=600,resizable=yes,location=no,menubar=no,toolbar=no,scrollbars=yes'); +} + +function open_aboutLC() { + var isMobile = "$env{'browser.mobile'}"; + var url = '/adm/about.html'; + if (isMobile == 1) { + openMyModal(url,600,400,'yes'); + } else { + window.open(url,"aboutLONCAPA","height=400,width=600,scrollbars=1,resizable=1,menubar=0,location=1"); + } + return; +} + + (function (\$) { \$(document).ready(function () { \$.single=function(a){return function(b){a[0]=b;return a}}(\$([1])); @@ -1781,8 +2411,12 @@ ENDUTILITY } sub serverform { + my $target; + unless (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) { + $target = ' target="_top"'; + } return(< + @@ -1792,15 +2426,20 @@ ENDSERVERFORM } sub constspaceform { + my ($target,$printtarget); + unless (($env{'request.lti.login'}) && ($env{'request.lti.target'} eq 'iframe')) { + $target = ' target="_top"'; + $printtarget = ' target="_parent"'; + } return(< + -
    +
    -
    + @@ -1820,25 +2459,25 @@ sub hidden_button_check { } sub roles_selector { - my ($cdom,$cnum,$httphost) = @_; + my ($cdom,$cnum,$httphost,$ltitarget) = @_; my $crstype = &Apache::loncommon::course_type(); my $now = time; - my (%courseroles,%seccount,%courseprivs); + my (%courseroles,%seccount,%courseprivs,%roledesc); my $is_cc; - my ($js,$form,$switcher,$switchtext); + my ($js,$form,$switcher,$has_opa_priv); my $ccrole; if ($crstype eq 'Community') { $ccrole = 'co'; } else { $ccrole = 'cc'; } - my ($priv,$gotsymb,$destsymb); + my ($privref,$gotsymb,$destsymb); my $destinationurl = $ENV{'REQUEST_URI'}; - if ($destinationurl =~ /\?symb=/) { + if ($destinationurl =~ /(\?|\&)symb=/) { $gotsymb = 1; } elsif ($destinationurl =~ m{^/enc/}) { my $plainurl = &Apache::lonenc::unencrypted($destinationurl); - if ($plainurl =~ /\?symb=/) { + if ($plainurl =~ /(\?|\&)symb=/) { $gotsymb = 1; } } @@ -1853,12 +2492,15 @@ sub roles_selector { my $destination = $destinationurl; $destination =~ s/(\?.*)$//; if (exists($reqprivs->{$destination})) { - $priv = $reqprivs->{$destination}; + if ($reqprivs->{$destination} =~ /,/) { + @{$privref} = split(/,/,$reqprivs->{$destination}); + } else { + $privref = [$reqprivs->{$destination}]; + } } } if ($env{'user.role.'.$ccrole.'./'.$cdom.'/'.$cnum}) { my ($start,$end) = split(/\./,$env{'user.role.'.$ccrole.'./'.$cdom.'/'.$cnum}); - if ((($start) && ($start<0)) || (($end) && ($end<$now)) || (($start) && ($now<$start))) { @@ -1868,7 +2510,9 @@ sub roles_selector { } } if ($is_cc) { - &get_all_courseroles($cdom,$cnum,\%courseroles,\%seccount,\%courseprivs,$priv); + &get_all_courseroles($cdom,$cnum,\%courseroles,\%seccount,\%courseprivs); + } elsif ($env{'request.role'} =~ m{^\Qcr/$cdom/$cdom-domainconfig/\E(\w+)\.\Q/$cdom/$cnum\E}) { + &get_customadhoc_roles($cdom,$cnum,\%courseroles,\%seccount,\%courseprivs,\%roledesc,$privref); } else { my %gotnosection; foreach my $item (keys(%env)) { @@ -1884,7 +2528,7 @@ sub roles_selector { $gotnosection{$role} = 1; } } - if ($priv ne '') { + if ((ref($privref) eq 'ARRAY') && (@{$privref} > 0)) { my $cnumsec = $cnum; if ($sec ne '') { $cnumsec .= "/$sec"; @@ -1913,7 +2557,6 @@ sub roles_selector { } } } - $switchtext = 'Switch role'; # do not translate here my @roles_order = ($ccrole,'in','ta','ep','ad','st'); my $numdiffsec; if (keys(%seccount) == 1) { @@ -1923,7 +2566,7 @@ sub roles_selector { } if ((keys(%seccount) > 1) || ($numdiffsec > 1)) { my @submenu; - $js = &jump_to_role($cdom,$cnum,\%seccount,\%courseroles,\%courseprivs,$priv); + $js = &jump_to_role($cdom,$cnum,\%seccount,\%courseroles,\%courseprivs,\%roledesc,$privref); $form = ''."\n". ' 0) { - $switcher = &create_submenu('','',$switchtext,\@submenu); + $switcher = &create_submenu('','',&mt('Switch role'),\@submenu,'','',$ltitarget); } } - return ($js,$form,$switcher); + return ($js,$form,$switcher,$has_opa_priv); } sub get_all_courseroles { @@ -2045,8 +2707,101 @@ sub get_all_courseroles { return; } +sub get_customadhoc_roles { + my ($cdom,$cnum,$courseroles,$seccount,$courseprivs,$roledesc,$privref) = @_; + unless ((ref($courseroles) eq 'HASH') && (ref($seccount) eq 'HASH') && + (ref($courseprivs) eq 'HASH') && (ref($roledesc) eq 'HASH')) { + return; + } + my $is_helpdesk = 0; + my $now = time; + foreach my $role ('dh','da') { + if ($env{"user.role.$role./$cdom/"}) { + my ($start,$end)=split(/\./,$env{"user.role.$role./$cdom/"}); + if (!($start && ($now<$start)) && !($end && ($now>$end))) { + $is_helpdesk = 1; + last; + } + } + } + if ($is_helpdesk) { + my ($possroles,$description) = &Apache::lonnet::get_my_adhocroles($cdom.'_'.$cnum); + my %available; + if (ref($possroles) eq 'ARRAY') { + map { $available{$_} = 1; } @{$possroles}; + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom); + if (ref($domdefaults{'adhocroles'}) eq 'HASH') { + if (keys(%{$domdefaults{'adhocroles'}})) { + my $numsec = 1; + my @sections; + my ($allseclist,$cached) = + &Apache::lonnet::is_cached_new('courseseclist',$cdom.'_'.$cnum); + if (defined($cached)) { + if ($allseclist ne '') { + @sections = split(/,/,$allseclist); + $numsec += scalar(@sections); + } + } else { + my %sections_count = &Apache::loncommon::get_sections($cdom,$cnum); + @sections = sort(keys(%sections_count)); + $numsec += scalar(@sections); + $allseclist = join(',',@sections); + &Apache::lonnet::do_cache_new('courseseclist',$cdom.'_'.$cnum,$allseclist); + } + my (%adhoc,$gotprivs); + my $prefix = "cr/$cdom/$cdom".'-domainconfig'; + foreach my $role (keys(%{$domdefaults{'adhocroles'}})) { + next if (($role eq '') || ($role =~ /\W/)); + $seccount->{"$prefix/$role"} = $numsec; + $roledesc->{"$prefix/$role"} = $description->{$role}; + if ((ref($privref) eq 'ARRAY') && (@{$privref} > 0)) { + if (exists($env{"user.priv.$prefix/$role./$cdom/$cnum./"})) { + $courseprivs->{"$prefix/$role./$cdom/$cnum./"} = + $env{"user.priv.$prefix/$role./$cdom/$cnum./"}; + $courseprivs->{"$prefix/$role./$cdom/$cnum./$cdom/"} = + $env{"user.priv.$prefix/$role./$cdom/$cnum./$cdom/"}; + $courseprivs->{"$prefix/$role./$cdom/$cnum./$cdom/$cnum"} = + $env{"user.priv.$prefix/$role./$cdom/$cnum./$cdom/$cnum"}; + } else { + unless ($gotprivs) { + my ($adhocroles,$privscached) = + &Apache::lonnet::is_cached_new('adhocroles',$cdom); + if ((defined($privscached)) && (ref($adhocroles) eq 'HASH')) { + %adhoc = %{$adhocroles}; + } else { + my $confname = &Apache::lonnet::get_domainconfiguser($cdom); + my %roledefs = &Apache::lonnet::dump('roles',$cdom,$confname,'rolesdef_'); + foreach my $key (keys(%roledefs)) { + (undef,my $rolename) = split(/_/,$key); + if ($rolename ne '') { + my ($systempriv,$domainpriv,$coursepriv) = split(/\_/,$roledefs{$key}); + $coursepriv = &Apache::lonnet::course_adhocrole_privs($rolename,$cdom,$cnum,$coursepriv); + $adhoc{$rolename} = join('_',($systempriv,$domainpriv,$coursepriv)); + } + } + &Apache::lonnet::do_cache_new('adhocroles',$cdom,\%adhoc); + } + $gotprivs = 1; + } + ($courseprivs->{"$prefix/$role./$cdom/$cnum./"}, + $courseprivs->{"$prefix/$role./$cdom/$cnum./$cdom/"}, + $courseprivs->{"$prefix/$role./$cdom/$cnum./$cdom/$cnum"}) = + split(/\_/,$adhoc{$role}); + } + } + if ($available{$role}) { + $courseroles->{"$prefix/$role"} = \@sections; + } + } + } + } + } + return; +} + sub jump_to_role { - my ($cdom,$cnum,$seccount,$courseroles,$courseprivs,$priv) = @_; + my ($cdom,$cnum,$seccount,$courseroles,$courseprivs,$roledesc,$privref) = @_; my %lt = &Apache::lonlocal::texthash( this => 'This role has section(s) associated with it.', ente => 'Enter a specific section.', @@ -2079,29 +2834,26 @@ sub jump_to_role { } } my $checkroles = 0; - if ($priv && ref($courseprivs) eq 'HASH') { - my (%disallowed,%allowed,@disallow); + if ((ref($privref) eq 'ARRAY') && (@{$privref} > 0) && (ref($courseprivs) eq 'HASH')) { + my %disallowed; foreach my $role (sort(keys(%{$courseprivs}))) { my $trole; if ($role =~ m{^(.+?)\Q./$cdom/$cnum\E}) { $trole = $1; } if (($trole ne '') && ($trole ne 'cm')) { - if ($courseprivs->{$role} =~ /\Q:$priv\E($|:|\&\w+)/) { - $allowed{$trole} = 1; - } else { - $disallowed{$trole} = 1; + $disallowed{$trole} = 1; + foreach my $priv (@{$privref}) { + if ($courseprivs->{$role} =~ /\Q:$priv\E($|:|\&\w+)/) { + delete($disallowed{$trole}); + last; + } } } } - foreach my $trole (keys(%disallowed)) { - unless ($allowed{$trole}) { - push(@disallow,$trole); - } - } - if (@disallow > 0) { + if (keys(%disallowed) > 0) { $checkroles = 1; - $js .= " var disallow = new Array('".join("','",@disallow)."');\n". + $js .= " var disallow = new Array('".join("','",keys(%disallowed))."');\n". " var rolecheck = 1;\n"; } } @@ -2198,13 +2950,13 @@ END sub required_privs { my $privs = { - '/adm/parmset' => 'opa', - '/adm/courseprefs' => 'opa', + '/adm/parmset' => 'opa,vpa', + '/adm/courseprefs' => 'opa,vpa', '/adm/whatsnew' => 'whn', - '/adm/populate' => 'cst', + '/adm/populate' => 'cst,vpa,vcl', '/adm/trackstudent' => 'vsa', - '/adm/statistics' => 'vgr', - '/adm/setblock' => 'dcm', + '/adm/statistics' => 'mgr,vgr', + '/adm/setblock' => 'dcm,vcb', '/adm/coursedocs' => 'mdc', }; unless ($env{'course.'.$env{'request.course.id'}.'.grading'} eq 'spreadsheet') { @@ -2217,16 +2969,26 @@ sub required_privs { sub countdown_timer { if (($env{'request.course.id'}) && ($env{'request.symb'} ne '') && - ($env{'request.filename'}=~/$LONCAPA::assess_re/)) { + (($env{'request.filename'}=~/$LONCAPA::assess_re/) || + (($env{'request.symb'} =~ /ext\.tool$/) && + (&Apache::lonnet::EXT('resource.0.gradable',$env{'request.symb'}) =~ /^yes$/i)))) { my ($type,$hastimeleft,$slothastime); my $now = time; if ($env{'request.filename'} =~ /\.task$/) { $type = 'Task'; + } elsif ($env{'request.symb'} =~ /ext\.tool$/) { + $type = 'tool'; } else { $type = 'problem'; } - my ($status,$accessmsg,$slot_name,$slot) = - &Apache::lonhomework::check_slot_access('0',$type); + my ($status,$accessmsg,$slot_name,$slot); + if ($type eq 'tool') { + ($status,$accessmsg,$slot_name,$slot) = + &Apache::lonhomework::check_slot_access('0',$type,$env{'request.symb'},['0']); + } else { + ($status,$accessmsg,$slot_name,$slot) = + &Apache::lonhomework::check_slot_access('0',$type); + } if ($slot_name ne '') { if (ref($slot) eq 'HASH') { if (($slot->{'starttime'} < $now) && @@ -2240,10 +3002,20 @@ sub countdown_timer { } my $duedate = &Apache::lonnet::EXT("resource.0.duedate"); my @interval=&Apache::lonnet::EXT("resource.0.interval"); + my ($timelimit,$usesdone,$donebuttontext,$proctor,$secret); if (@interval > 1) { + ($timelimit,my $donesuffix) = split(/_/,$interval[0],2); + if ($donesuffix =~ /^done\:([^\:]+)\:(.*)$/) { + $usesdone = 'done'; + $donebuttontext = $1; + (undef,$proctor,$secret) = split(/_/,$2); + } elsif ($donesuffix =~ /^done(|_.+)$/) { + $donebuttontext = &mt('Done'); + ($usesdone,$proctor,$secret) = split(/_/,$donesuffix); + } my $first_access=&Apache::lonnet::get_first_access($interval[1]); if ($first_access > 0) { - if ($first_access+$interval[0] > time) { + if ($first_access+$timelimit > time) { $hastimeleft = 1; } } @@ -2256,9 +3028,11 @@ sub countdown_timer { ($type eq 'Task' && $slothastime)) { $currdisp = 'inline'; $collapse = '► '; - $donebutton = &done_button_js(); - - + if ((@interval > 1) && ($hastimeleft)) { + if ($usesdone eq 'done') { + $donebutton = &done_button_js($interval[1],'','',$proctor,$donebuttontext); + } + } } else { $currdisp = 'none'; $expand = '◄ '; @@ -2270,7 +3044,7 @@ sub countdown_timer { my $desc = &mt('Countdown to due date/time'); return < $collapse @@ -2279,13 +3053,19 @@ $collapse $expand $alttxt$title -$donebutton END } } return; } +sub placement_progress { + my ($totalpoints,$incomplete) = &Apache::lonplacementtest::check_completion(undef,undef,1); + my $complete = 100 - $incomplete; + return ''. + &mt('Test is [_1]% complete',$complete).''; +} + # ================================================================ Main Program BEGIN { @@ -2302,14 +3082,17 @@ BEGIN { $category_positions{$entries[2]}=$entries[1]; $category_names{$entries[2]}=$entries[3]; } elsif ($configline=~/^prim\:/) { - my @entries = (split(/\:/, $configline))[1..6]; + my @entries = (split(/\:/, $configline))[1..7]; push(@primary_menu,\@entries); } elsif ($configline=~/^primsub\:/) { - my ($parent,@entries) = (split(/\:/, $configline))[1..4]; + my ($parent,@entries) = (split(/\:/, $configline))[1..5]; push(@{$primary_submenu{$parent}},\@entries); } elsif ($configline=~/^scnd\:/) { - my @entries = (split(/\:/, $configline))[1..5]; + my @entries = (split(/\:/, $configline))[1..6]; push(@secondary_menu,\@entries); + } elsif ($configline=~/^scndsub\:/) { + my ($parent,@entries) = (split(/\:/, $configline))[1..4]; + push(@{$secondary_submenu{$parent}},\@entries); } elsif ($configline) { push(@desklines,$configline); }