--- loncom/interface/lonhelper.pm 2010/03/10 21:25:50 1.179 +++ loncom/interface/lonhelper.pm 2022/06/11 14:51:49 1.203 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # .helper XML handler to implement the LON-CAPA helper # -# $Id: lonhelper.pm,v 1.179 2010/03/10 21:25:50 droeschl Exp $ +# $Id: lonhelper.pm,v 1.203 2022/06/11 14:51:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -64,9 +64,11 @@ messages, resource selections, or date q The helper tag is required to have one attribute, "title", which is the name of the helper itself, such as "Parameter helper". The helper tag may optionally -have a "requiredpriv" attribute, specifying the priviledge a user must have +have a "requiredpriv" attribute, specifying the privilege a user must have to use the helper, or get denied access. See loncom/auth/rolesplain.tab for -useful privs. Default is full access, which is often wrong! +useful privs. You may add the modifier &S at the end of the three letter priv +if you want to grant access to users for whom the corresponding privilege is +section-specific. The default is full access, which is often wrong! =head2 State tags @@ -261,7 +263,7 @@ sub real_handler { my $uri = shift; if (!defined($uri)) { $uri = $r->uri(); } $env{'request.uri'} = $uri; - my $filename = '/home/httpd/html' . $uri; + my $filename = $r->dir_config('lonDocRoot').$uri; my $fh = Apache::File->new($filename); my $file; read $fh, $file, 100000000; @@ -283,7 +285,8 @@ sub real_handler { my $allowed = $helper->allowedCheck(); if (!$allowed) { - $env{'user.error.msg'} = $env{'request.uri'}.':'.$helper->{REQUIRED_PRIV}. + my ($priv,$modifier) = split(/\&/,$helper->{REQUIRED_PRIV}); + $env{'user.error.msg'} = $env{'request.uri'}.':'.$priv. ":0:0:Permission denied to access this helper."; return HTTP_NOT_ACCEPTABLE; } @@ -508,8 +511,13 @@ sub allowedCheck { if (!defined($self->{REQUIRED_PRIV})) { return 1; } - - return Apache::lonnet::allowed($self->{REQUIRED_PRIV}, $env{'request.course.id'}); + my ($priv,$modifier) = split(/\&/,$self->{REQUIRED_PRIV}); + my $allowed = &Apache::lonnet::allowed($priv,$env{'request.course.id'}); + if ((!$allowed) && ($modifier eq 'S') && ($env{'request.course.sec'} ne '')) { + $allowed = &Apache::lonnet::allowed($priv,$env{'request.course.id'}.'/'. + $env{'request.course.sec'}); + } + return $allowed; } sub changeState { @@ -571,7 +579,7 @@ sub process { # 4: Render the current state to the screen as an HTML page. sub display { my $self = shift; - + my $footer = shift; my $state = $self->{STATES}{$self->{STATE}}; my $result = ""; @@ -606,7 +614,7 @@ sub display { # FIXME: This should be parameterized, not concatenated - Jeremy - if (!$state->overrideForm()) { $result.='
'; } + if (!$state->overrideForm()) { $result.=''; } if ($stateHelp) { $stateHelp = &Apache::loncommon::help_open_topic($stateHelp); } @@ -651,7 +659,7 @@ sub display { $result .= $buttons; - #foreach my $key (keys %{$self->{VARS}}) { + #foreach my $key (keys(%{$self->{VARS}})) { # $result .= "|$key| -> " . $self->{VARS}->{$key} . "
"; #} @@ -661,7 +669,7 @@ sub display {
FOOTER - $result .= &Apache::loncommon::end_page(); + $result .= $footer.&Apache::loncommon::end_page(); # Handle writing out the vars to the file my $file = Apache::File->new('>'.$self->{FILENAME}); print $file $self->_varsInFile(); @@ -1476,9 +1484,9 @@ BUTTONS HTML::Entities::encode($choice->[1],"<>&\"'") . "'"; if ($checkedChoices{$choice->[1]}) { - $result .= " checked='checked' "; + $result .= " checked='checked'"; } - $result .= qq{id="id$id"}; + $result .= qq{ id="id$id"}; my $choiceLabel = $choice->[0]; if ($choice->[3]) { # if we need to evaluate this choice $choiceLabel = "sub { my $helper = shift; my $state = shift;" . @@ -1486,7 +1494,7 @@ BUTTONS $choiceLabel = eval($choiceLabel); $choiceLabel = &$choiceLabel($helper, $self); } - $result .= "/> ".qq{' } return $result; @@ -2033,7 +2047,9 @@ be filtered out. The 'addstatus' attribu and long status display columns to the display. The 'addparts' attribute will add in a part selector beside problems that have more than 1 part. The 'includecourse' attribute if true, will include -the toplevel default.sequence in the results. +the toplevel default.sequence in the results. The 'modalLink' attribute, +if true, will cause links to be launched as modal pop-ups, instead of +replacing the resource selection listing, currently being displayed. =head3 SUB-TAGS @@ -2145,6 +2161,9 @@ sub start_resource { $paramHash->{'toponly'} = $token->[2]{'toponly'}; $paramHash->{'addstatus'} = $token->[2]{'addstatus'}; $paramHash->{'addparts'} = $token->[2]{'addparts'}; + $paramHash->{'modalLink'} = $token->[2]{'modallink'}; + $paramHash->{'nocurrloc'} = $token->[2]{'nocurrloc'}; + $paramHash->{'suppressNavmap'} = $token->[2]{'suppressNavmap'}; if ($paramHash->{'addparts'}) { $helper->declareVar($paramHash->{'variable'}.'_part'); } @@ -2351,6 +2370,7 @@ BUTTONS my $option_texts = $self->{OPTION_TEXTS}; my $option_types = $self->{OPTION_TYPES}; my $addparts = $self->{'addparts'}; + my $modalLink = $self->{'modalLink'}; my $headings_done = 0; # Evaluate the map url as needed @@ -2390,7 +2410,7 @@ BUTTONS $result .= "$text"; } } - $result .= "Select"; + $result .= ''.&Apache::lonlocal::mt('Select').''; $result .= ""; # Close off the extra row and start a new one. $headings_done = 1; } @@ -2450,7 +2470,7 @@ BUTTONS $resource_name . "'/> "; } else { $col .= - " "; } @@ -2487,9 +2507,9 @@ BUTTONS &HTML::Entities::encode(&$valueFunc($resource),"<>&\"'"); if ($addparts && (scalar(@{$resource->parts}) > 1)) { $col .= ""; } @@ -2534,7 +2554,11 @@ RADIO 'closeAllPages' => $self->{'closeallpages'}, 'suppressEmptySequences' => $self->{'suppressEmptySequences'}, 'include_top_level_map' => $self->{'include_top_level_map'}, - 'iterator_map' => $mapUrl } + 'iterator_map' => $mapUrl, + 'map_no_edit_link' => 1, + 'modalLink' => $modalLink, + 'nocurrloc' => $self->{'nocurrloc'}, + 'suppressNavmap' => $self->{'suppressNavmap'}, } ); $result .= $buttons; @@ -2606,6 +2630,12 @@ selection. Defaults to false. If true, only active students and course personnel will be shown. Defaults to false. +=item * B: + +If true, and user's role is in a specific section, only course personnel +will be shown if they also have a section-specific role in the same section. +Defaults to false. + =item * B: If true, the selection of no users is allowed. Defaults to false. @@ -2641,6 +2671,7 @@ sub start_student { $helper->declareVar($paramHash->{'variable'}); $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; $paramHash->{'coursepersonnel'} = $token->[2]{'coursepersonnel'}; + $paramHash->{'sectiononly'} = $token->[2]{'sectiononly'}; $paramHash->{'activeonly'} = $token->[2]{'activeonly'}; if (defined($token->[2]{'nextstate'})) { $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; @@ -2682,14 +2713,17 @@ sub render { delete($defaultUsers{''}); } + my $personnel_section; + if ($self->{'sectiononly'}) { + $personnel_section = $env{'request.course.sec'}; + } my ($course_personnel, $current_members, $expired_members, $future_members) = - &Apache::lonselstudent::get_people_in_class($env{'request.course.sec'}); - - + &Apache::lonselstudent::get_people_in_class($env{'request.course.sec'}, + $personnel_section); # Load up the non-students, if necessary @@ -2697,26 +2731,44 @@ sub render { unshift @$current_members, (@$course_personnel); } + my %titles = &Apache::lonlocal::texthash( + 'active' => 'Select Currently Enrolled Students and Active Course Personnel', + 'future' => 'Select Future Enrolled Students', + 'expired' => 'Select Previously Enrolled Students', + ); + + if ($env{'request.course.sec'}) { + if ($self->{'sectiononly'}) { + $titles{'active'} = &mt('Select Currently Enrolled Students and Active Course Personnel in Section: [_1]', + $env{'request.course.sec'}); + } else { + $titles{'active'} = &mt('Select Currently Enrolled Students in Section: [_1], and Active Course Personnel', + $env{'request.course.sec'}); + } + $titles{'future'} = &mt('Select Future Enrolled Students in Section: [_1]', + $env{'request.course.sec'}); + $titles{'expired'} = &mt('Select Previously Enrolled Students in Section: [_1]', + $env{'request.course.sec'}); + } - # Current personel + # Current personnel - $result .= '

'.&mt('Select Currently Enrolled Students and Active Course Personnel').'

'; + $result .= '

'.$titles{'active'}.'

'; $result .= &Apache::lonselstudent::render_student_list( $current_members, "helpform", - "current", + "current", \%defaultUsers, $self->{'multichoice'}, $self->{'variable'}, 1); - # If activeonly is not set then we can also give the expired students: # if (!$self->{'activeonly'} && ((scalar(@$future_members)) > 0)) { # And future. - $result .= '

'.&mt('Select Future Enrolled Students and Future Course Personnel').'

'; + $result .= '

'.$titles{'future'}.'

'; $result .= &Apache::lonselstudent::render_student_list( $future_members, "helpform", @@ -2729,7 +2781,7 @@ sub render { if (!$self->{'activeonly'} && ((scalar(@$expired_members)) > 0)) { # Past - $result .= '

'.&mt('Select Previously Enrolled Students and Inactive Course Personnel').'

'; + $result .= '

'.$titles{'expired'}.'

'; $result .= &Apache::lonselstudent::render_student_list($expired_members, "helpform", "past", @@ -2971,30 +3023,31 @@ BUTTONS } # Get the list of files in this directory. - my @fileList; + my (@fileList,$listref,$listerror); # If the subdirectory is in local CSTR space my $metadir; - if ($subdir =~ m|/home/([^/]+)/public_html/(.*)|) { - my ($user,$domain)= - &Apache::loncacc::constructaccess($subdir, - $Apache::lonnet::perlvar{'lonDefDomain'}); - $metadir='/res/'.$domain.'/'.$user.'/'.$2; - @fileList = &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/'); - } elsif ($subdir =~ m|^~([^/]+)/(.*)$|) { - $subdir='/home/'.$1.'/public_html/'.$2; + my $londocroot = $Apache::lonnet::perlvar{'lonDocRoot'}; + if ($subdir =~ m{^(?:\Q$londocroot\E)*/priv/[^/]+/[^/]+/(.*)$}) { + my $innerpath=$1; + unless ($subdir=~m{^\Q$londocroot\E}) { + $subdir=$londocroot.$subdir; + } my ($user,$domain)= - &Apache::loncacc::constructaccess($subdir, - $Apache::lonnet::perlvar{'lonDefDomain'}); - $metadir='/res/'.$domain.'/'.$user.'/'.$2; - @fileList = &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/'); + &Apache::lonnet::constructaccess($subdir); + $metadir='/res/'.$domain.'/'.$user.'/'.$innerpath; + ($listref,$listerror) = + &Apache::lonnet::dirlist($subdir,$domain,$user,undef,undef,'/'); } else { # local library server resource space - @fileList = &Apache::lonnet::dirlist($subdir,$env{'user.domain'},$env{'user.name'},undef,undef,'/'); + ($listref,$listerror) = + &Apache::lonnet::dirlist($subdir,$env{'user.domain'},$env{'user.name'},undef,undef,'/'); } # Sort the fileList into order - @fileList = sort {lc($a) cmp lc($b)} @fileList; + if (ref($listref) eq 'ARRAY') { + @fileList = sort {lc($a) cmp lc($b)} @{$listref}; + } $result .= $buttons; @@ -3089,7 +3142,7 @@ sub fileState { } my $docroot = $Apache::lonnet::perlvar{'lonDocRoot'}; my $subdirpart = $constructionSpaceDir; - $subdirpart =~ s/^\/home\/$uname\/public_html//; + $subdirpart =~ s{^\Q$docroot/priv/$udom/$uname\E}{}; my $resdir = $docroot . '/res/' . $udom . '/' . $uname . $subdirpart; @@ -3139,7 +3192,7 @@ It takes the standard attributes "variab "allowempty" and "nextstate", meaning what they do for most other elements. -also takes a boolean 'onlysections' whcih will restrict this to only +also takes a boolean 'onlysections' which will restrict this to only have sections and not include groups =cut @@ -3177,22 +3230,27 @@ sub start_section { # Populate the CHOICES element my %choices; + my $usersec = $Apache::lonnet::env{'request.course.sec'}; - my $section = Apache::loncoursedata::CL_SECTION(); - my $classlist = Apache::loncoursedata::get_classlist(); - foreach my $user (keys(%$classlist)) { - my $section_name = $classlist->{$user}[$section]; - if (!$section_name) { - $choices{"No section assigned"} = ""; - } else { - $choices{$section_name} = $section_name; + if ($usersec ne '') { + $choices{$usersec} = $usersec; + } else { + my $section = Apache::loncoursedata::CL_SECTION(); + my $classlist = Apache::loncoursedata::get_classlist(); + foreach my $user (keys(%$classlist)) { + my $section_name = $classlist->{$user}[$section]; + if (!$section_name) { + $choices{"No section assigned"} = ""; + } else { + $choices{$section_name} = $section_name; + } + } + + if (exists($choices{"No section assigned"})) { + push(@{$paramHash->{CHOICES}}, + ['No section assigned','No section assigned']); + delete($choices{"No section assigned"}); } - } - - if (exists($choices{"No section assigned"})) { - push(@{$paramHash->{CHOICES}}, - ['No section assigned','No section assigned']); - delete($choices{"No section assigned"}); } for my $section_name (sort {lc($a) cmp lc($b) } (keys(%choices))) { push @{$paramHash->{CHOICES}}, [$section_name, $section_name]; @@ -3228,6 +3286,10 @@ It takes the standard attributes "variab "allowempty" and "nextstate", meaning what they do for most other elements. +also takes a boolean grouponly, which if true, will restrict choice to +groups in which user is a member, unless user has the mdg priv in the course, +in which case all groups will be possible choices. Defaults to false. + =cut no strict; @@ -3257,6 +3319,7 @@ sub start_group { $helper->declareVar($paramHash->{'variable'}); $paramHash->{'multichoice'} = $token->[2]{'multichoice'}; $paramHash->{'allowempty'} = $token->[2]{'allowempty'}; + $paramHash->{'grouponly'} = $token->[2]{'grouponly'}; if (defined($token->[2]{'nextstate'})) { $paramHash->{NEXTSTATE} = $token->[2]{'nextstate'}; } @@ -3264,7 +3327,12 @@ sub start_group { # Populate the CHOICES element my %choices; - my %curr_groups = &Apache::longroup::coursegroups(); + my %curr_groups; + if ((!$paramHash->{'grouponly'}) || (&Apache::lonnet::allowed('mdg',$Apache::lonnet::env{'request.course.id'}))) { + %curr_groups = &Apache::longroup::coursegroups(); + } elsif ($Apache::lonnet::env{'request.course.groups'} ne '') { + map { $curr_groups{$_} = 1; } split(/:/,$Apache::lonnet::env{'request.course.groups'}); + } foreach my $group_name (sort {lc($a) cmp lc($b)} (keys(%curr_groups))) { push(@{$paramHash->{CHOICES}}, [$group_name, $group_name]); } @@ -3289,6 +3357,10 @@ package Apache::lonhelper::string; string elements provide a string entry field for the user. string elements take the usual 'variable' and 'nextstate' parameters. string elements also pass through 'maxlength' and 'size' attributes to the input tag. +Since you could have multiple strings in a helper state, each with its own +validator, all but the last string should have +noproceed='1' so that _all_ validators are evaluated before the next +state can be reached. string honors the defaultvalue tag, if given. @@ -3308,6 +3380,7 @@ BEGIN { sub new { my $ref = Apache::lonhelper::element->new(); + $ref->{'PROCEED'} = 1; # By default postprocess goes to next state. bless($ref); } @@ -3324,20 +3397,33 @@ sub start_string { $paramHash->{'nextstate'} = $token->[2]{'nextstate'}; $paramHash->{'maxlength'} = $token->[2]{'maxlength'}; $paramHash->{'size'} = $token->[2]{'size'}; - return ''; } sub end_string { my ($target,$token,$tagstack,$parstack,$parser,$safeeval,$style)=@_; + if ($target ne 'helper') { return ''; } - Apache::lonhelper::string->new(); + my $state = Apache::lonhelper::string->new(); + + + if(&Apache::lonxml::get_param('noproceed', $parstack, $safeeval, undef, 1)) { + $state->noproceed(); + } + + + return ''; } +sub noproceed() { + my $self = shift; + $self->{PROCEED} = 0; +} + sub render { my $self = shift; my $result = ''; @@ -3346,7 +3432,7 @@ sub render { $result .= '

' . $self->{ERROR_MSG} . '

'; } - $result .= '{'variable'} . '_forminput"'; if (defined($self->{'size'})) { $result .= ' size="' . $self->{'size'} . '"'; @@ -3381,7 +3467,7 @@ sub postprocess { } } - if (defined($self->{'nextstate'})) { + if (defined($self->{'nextstate'}) && $self->{PROCEED}) { $helper->changeState($self->{'nextstate'}); } @@ -3608,7 +3694,7 @@ sub render { my @results; # Collect all the results - for my $stateName (keys %{$helper->{STATES}}) { + for my $stateName (keys(%{$helper->{STATES}})) { my $state = $helper->{STATES}->{$stateName}; for my $element (@{$state->{ELEMENTS}}) { @@ -3749,38 +3835,54 @@ sub render { if ($vars->{GRANULARITY} eq 'whole_course') { $resourceString .= '
  • '.&mt('for [_1]all resources in the course[_2]','','').'
  • '; if ($vars->{TARGETS} eq 'course') { - $level = 14; # general course, see lonparmset.pm perldoc + $level = 18; # general course, see lonparmset.pm perldoc } elsif ($vars->{TARGETS} eq 'section') { - $level = 9; + $level = 12; } elsif ($vars->{TARGETS} eq 'group') { - $level = 6; + $level = 8; } else { - $level = 3; + $level = 4; } $affectedResourceId = "0.0"; $symb = 'a'; $paramlevel = 'general'; - } elsif ($vars->{GRANULARITY} eq 'map') { + } elsif (($vars->{GRANULARITY} eq 'map') || ($vars->{GRANULARITY} eq 'maprecurse')) { my $navmap = Apache::lonnavmaps::navmap->new(); if (defined($navmap)) { my $res = $navmap->getByMapPc($vars->{RESOURCE_ID}); my $title = $res->compTitle(); $symb = $res->symb(); - $resourceString .= '
  • '.&mt('for the map named [_1]',"$title").'
  • '; + if ($vars->{GRANULARITY} eq 'map') { + $resourceString .= '
  • '.&mt('for the map named [_1]',"$title").'
  • '; + } else { + $resourceString .= '
  • '.&mt('for the map named [_1] (applies recursively to sub-folders)',"$title").'
  • '; + } } else { $resourceString .= '
  • '.&mt('for the map ID [_1] (name unavailable)',''.$vars->{RESOURCE_ID}.'').'
  • '; &Apache::lonnet::logthis('Retrieval of map title failed in lonhelper.pm - could not create navmap object for course.'); } - if ($vars->{TARGETS} eq 'course') { - $level = 13; # general course, see lonparmset.pm perldoc - } elsif ($vars->{TARGETS} eq 'section') { - $level = 8; - } elsif ($vars->{TARGETS} eq 'group') { - $level = 5; - } else { - $level = 2; - } + if ($vars->{GRANULARITY} eq 'maprecurse') { + if ($vars->{TARGETS} eq 'course') { + $level = 17; # general course, see lonparmset.pm perldoc + } elsif ($vars->{TARGETS} eq 'section') { + $level = 11; + } elsif ($vars->{TARGETS} eq 'group') { + $level = 7; + } else { + $level = 3; + } + } else { + if ($vars->{TARGETS} eq 'course') { + $level = 16; # general course, see lonparmset.pm perldoc + } elsif ($vars->{TARGETS} eq 'section') { + $level = 10; + } elsif ($vars->{TARGETS} eq 'group') { + $level = 6; + } else { + $level = 2; + } + } $affectedResourceId = $vars->{RESOURCE_ID}; $paramlevel = 'map'; } else { @@ -3797,11 +3899,11 @@ sub render { &Apache::lonnet::logthis('Retrieval of resource title failed in lonhelper.pm - could not create navmap object for course.'); } if ($vars->{TARGETS} eq 'course') { - $level = 10; # general course, see lonparmset.pm perldoc + $level = 13; # general course, see lonparmset.pm perldoc } elsif ($vars->{TARGETS} eq 'section') { - $level = 7; + $level = 9; } elsif ($vars->{TARGETS} eq 'group') { - $level = 4; + $level = 5; } else { $level = 1; } @@ -3817,7 +3919,7 @@ sub render { if ($vars->{GRANULARITY} eq 'resource') { $result .= "&\"") . "' />\n"; - } elsif ($vars->{GRANULARITY} eq 'map') { + } elsif (($vars->{GRANULARITY} eq 'map') || ($vars->{GRANULARITY} eq 'maprecurse')) { $result .= "\n"; }