--- loncom/interface/lonblockingmenu.pm 2012/04/04 21:04:56 1.5 +++ loncom/interface/lonblockingmenu.pm 2016/01/27 03:04:49 1.17 @@ -2,7 +2,7 @@ # Routines for configuring blocking of access to collaborative functions, # and specific resources during an exam # -# $Id: lonblockingmenu.pm,v 1.5 2012/04/04 21:04:56 raeburn Exp $ +# $Id: lonblockingmenu.pm,v 1.17 2016/01/27 03:04:49 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -57,6 +57,9 @@ into this category. (b) those which a student could use to access materials prepared by the student in advance of an exam, (e.g., for use during an online exam, to gain an unfair advantage). Blogs and portfolio fall into this category. +(c) those which a student could use to display or save content within +the course itself (outside the exam folder). Printouts and resources +fall into this category. For communication blocking to be truly effective in preventing unwanted communication, or access to online materials, online testing needs to @@ -64,7 +67,7 @@ take place in a lab setting where use of of web sites beyond LON-CAPA are unavailable. Access to specified folder(s) and/or resources in the course contents -can also be restricted for the duration of an exam. +can be restricted for the duration of an exam. Exam blocks are of two types: (a) Blocks with a defined start and end date. @@ -87,7 +90,7 @@ it is important to use reasonable time w the case of blocks triggered by clicking a button to start a timed quiz, quiz durations that are of limited duration. This is especially important when blocking prtfolio access, as other courses may require students to use -the portfolio as a mechanism for submitting assigments. +the portfolio as a mechanism for submitting assignments. Information about blocks in a course will be cached for 10 minutes, so, as with parameters set for resources, it can take up to 10 minutes for @@ -124,38 +127,21 @@ Output: 1 Hash Stores changes to exam blocks in comm_block.db file for course. Processes deletions, modifications and additions. -Inputs: 2 +Inputs: 4 + $r = request object + $crstype - Container type: Course or Community. $blockcount - Total number of blocking events in course. + $currblockrecs - Ref to hash of current blocks in course. + Outputs: 2 $changestotal - Total number of changes made. $output - Information about changes made. -=item &enumerate_course_contents() - -Create hashes of maps (for folders/pages) and symbs (for resources) in -a course, where keys are numbers (starting with 1) and values are -map url, or symb, for an iteration through the course, as seen by -a Course Coordinator. Used to generate numerical IDs to facilitate -storage of lists of maps or resources to be blocked during an exam. - -Inputs: 3 - $navmap - navmaps object - - $map_url - reference to hash to contain URLs of maps in course - - $resource_symb - reference to hash to contain symbs for - resources in course - -Outputs: None - -Side Effects: $map_url and $resource_symb hashrefs are populated. - - =item &get_dates_from_form() Extract start and end dates from web form input for blocks with @@ -181,7 +167,7 @@ Side Effects: populates records hashref. =item &get_block_choices() Extract information from web form about which communication/ -collaboration features are to be blocked, for a partilcuar event, +collaboration features are to be blocked, for a particular event, and also which content areas will have access blocked for the duration of the block. @@ -205,12 +191,30 @@ Update LON-CAPA version requirements for (content) or blocking type (triggered by student starting timer) require specific LON-CAPA version (i.e., 2.11). -Inputs: 1 - type of constraint (currently: 'docs' or 'timer'). +Inputs: 3 - $value - type of constraint (currently: 'docs', 'printout' or 'timer'), + $chomemajor - course's home server LON-CAPA major version number. + $chomeminor - course's home server LON-CAPA minor version number. -Outputs: None +Outputs: 2 - status ('ok' or 'fail') and LON-CAPA version needed. + +=over + + A status of 'fail' will be returned if the + LON-CAPA version installed on the course's + home server is older than the version + requirement for the blocking type. + For a trigger type event, the requested + blocking event will not be added if + the course's home server version is old to + support that type of block. + +=back Side Effects: &update_released_required() called in lonnet, if - needed to update version requirements for course. + course's home server version is requied version or + newer; will update version requirements for course to + a more recent version requirement than currently in + effect. =item &display_blocker_status() @@ -238,23 +242,6 @@ Output: None Side Effects: prints web form elements (in a table) for current blocks. -=item &path_to_trigger() - -Provides hierarchy of names of folders/sub-folders containing the current -item identified as an item with an interval timer set, to be used as a -trigger. - -Inputs: 3 - - $navmap - navmaps object - - - $map - url for map (either the trigger itself, or map containing - the resource, which is the trigger). - - - $type - type of trigger: map or resource. - -Outputs: 1 @pathitems - array of folder/subfolder names. - - =item &convlim() Convert a time interval used for a timed quiz (in seconds) to @@ -375,7 +362,8 @@ Output: 2 Create Javascript used to launch pop-up used for content selection, and to toggle visibility of a number of expandable/collapsible divs. -Inputs: 1 - $blockcount - +Inputs: 1 - $blockcount - Total number of blocks in course's comm_block.db + database file. Output: 1 - Javascript (with tags) for functions used to: (a) launch pop-up window for selection of course content to which @@ -405,11 +393,15 @@ use Apache::Constants qw(:common :http); use Apache::loncommon(); use Apache::lonhtmlcommon(); use Apache::lonparmset(); +use Apache::loncourserespicker(); use HTML::Entities(); use Apache::lonlocal; use lib '/home/httpd/lib/perl/'; use LONCAPA qw(:DEFAULT :match); +my $registered_cleanup; +my $modified_courses; + sub handler { my $r=shift; @@ -440,6 +432,9 @@ sub handler { # -----------------------------Get action and calling context from query string + $registered_cleanup=0; + @{$modified_courses}=(); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['action','caller','block']); @@ -486,7 +481,7 @@ sub handler { # -------------------------- Store changes and retrieve latest block information my $storeresult; if ($env{'form.action'} eq 'store') { - (my $numchanges,$storeresult) = &blockstore($crstype,$blockcount); + (my $numchanges,$storeresult) = &blockstore($r,$crstype,$blockcount,\%records); if ($numchanges > 0) { $blockcount = &get_blockdates(\%records); } @@ -506,7 +501,7 @@ sub handler { } &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/setblock', - text=>'Blocking communication/resource access'}); + text=>'Blocking communication/content access'}); my $js = &blockingmenu_javascript($blockcount); @@ -515,7 +510,6 @@ sub handler { &Apache::lonhtmlcommon::breadcrumbs('Blocking communication/content access')); my $usertype; - my $crstype = &Apache::loncommon::course_type(); if ($crstype eq 'Community') { $usertype = 'members'; } else { @@ -526,7 +520,7 @@ sub handler { 'cbds' => 'Blocking communication and/or content access during exams', 'prev' => "For the duration of an exam, or a timed quiz, students in this course can be prevented from:", 'blca' => "Blocks can potentially interrupt legitimate communication between $usertype who are also both enrolled in a different LON-CAPA $lctype.", - 'pobl' => "Portfolio blocking can impact a student's ability to complete assigments in courses besides your own. Please use this feature wisely.", + 'pobl' => "Portfolio blocking can impact a student's ability to complete assignments in courses besides your own. Please use this feature wisely.", 'actt' => "Action to take:", 'addn' => 'Add new blocking event', 'mexb' => 'Modify existing blocking event(s)', @@ -565,7 +559,9 @@ sub handler { '
  • '.&mt("displaying LON-CAPA messages sent by other $usertype in the $lctype").'
  • '."\n". '
  • '.&mt("displaying or posting to LON-CAPA discussion boards or live chat in the $lctype").'
  • '."\n". '
  • '.&mt('accessing content in LON-CAPA portfolios or blogs').'
  • '."\n". + '
  • '.&mt("generating printouts of $lctype content").'
  • '. '
  • '.&mt("accessing $lctype content in specified folders or resources").'
  • '. + '
  • '.&mt("changing user's own password").'
  • '. ''. '

    '.$lt{'blca'}.'
    '.$lt{'pobl'}.'

    ' ); @@ -666,7 +662,7 @@ sub get_timed_items { } sub blockstore { - my ($crstype,$blockcount) = @_; + my ($r,$crstype,$blockcount,$currblockrecs) = @_; my %lt=&Apache::lonlocal::texthash( 'tfcm' => 'The following changes were made', 'ncwm' => 'No changes were made.', @@ -681,7 +677,7 @@ sub blockstore { my $changestotal = 0; my $addtimer = 0; my %blocking = (); - my (%map_url,%resource_symb,$output); + my (%map_url,%resource_symb,%titles,$output); $output = '

    '.$lt{'head'}.'

    '; if ($env{'form.blockaction'} eq 'modify') { foreach my $envkey (keys(%env)) { @@ -707,17 +703,43 @@ sub blockstore { $output = $lt{'unna'}.' '.$lt{'ncwm'}.'
    '; return ($changestotal,$output); } - &enumerate_course_contents($navmap,\%map_url,\%resource_symb); + &Apache::loncourserespicker::enumerate_course_contents($navmap,\%map_url,\%resource_symb,\%titles,'examblock'); + my $do_releasereq_update; + my $cdom = $env{'course.'.$env{'request.course.id'}.'.domain'}; + my $cnum = $env{'course.'.$env{'request.course.id'}.'.num'}; + my $chome = $env{'course.'.$env{'request.course.id'}.'.home'}; + my $chostname = &Apache::lonnet::hostname($chome); + my ($chomemajor,$chomeminor) = + split(/\./,&Apache::lonnet::get_server_loncaparev($cdom,$chome)); + + foreach my $key (keys(%removals)) { my $hashkey = $env{'form.key_'.$key}; - &Apache::lonnet::del('comm_block',["$hashkey"], - $env{'course.'.$env{'request.course.id'}.'.domain'}, - $env{'course.'.$env{'request.course.id'}.'.num'} - ); + if ($hashkey =~ /firstaccess____/) { + $do_releasereq_update = 1; + } + if (ref($currblockrecs->{$hashkey}) eq 'HASH') { + if (ref($currblockrecs->{$hashkey}->{'blocks'}) eq 'HASH') { + foreach my $type ('docs','printout') { + if (exists($currblockrecs->{$hashkey}->{'blocks'}->{$type})) { + $do_releasereq_update = 1; + } + } + } + } + &Apache::lonnet::del('comm_block',["$hashkey"],$cdom,$cnum); + } + if ($do_releasereq_update) { + push(@{$modified_courses},[$cdom,$cnum,$chome,$crstype]); + unless ($registered_cleanup) { + my $handlers = $r->get_handlers('PerlCleanupHandler'); + $r->set_handlers('PerlCleanupHandler' => [\&update_releasereq,@{$handlers}]); + $registered_cleanup=1; + } } foreach my $key (keys(%adds)) { unless ( defined($cancels{$key}) ) { - my $newkey; + my ($newkey,$status,$needsrelease);; if ($env{'form.firstaccess_'.$key}) { my $interval = &HTML::Entities::decode($env{'form.firstaccess_'.$key}); @@ -735,7 +757,13 @@ sub blockstore { } if ($newkey ne '') { unless (defined($removals{$key})) { - $addtimer ++; + ($status,$needsrelease) = &check_release_required('timer',$chomemajor,$chomeminor); + if ($status eq 'fail') { + $newkey = ''; + $output .= '

    '. + &mt('Triggering of blocking events not allowed for [_1]', + &escape($env{'form.title_'.$key})).'
    '; + } } } } @@ -743,22 +771,38 @@ sub blockstore { my ($newstart,$newend) = &get_dates_from_form($key); $newkey = $newstart.'____'.$newend; } + if ($status eq 'fail') { + $output .= &mt('LON-CAPA version ([_1]) installed on home server ([_2]) does not meet version requirements ([_3] or newer).', + $chomemajor.'.'.$chomeminor,$chostname,$needsrelease).'

    '; + } if ($newkey ne '') { my ($blocktypes,$blockdocs) = &get_block_choices($key,\%map_url,\%resource_symb); + if (ref($blocktypes) eq 'HASH') { + if ($blocktypes->{'printout'} eq 'on') { + ($status,$needsrelease) = &check_release_required('printout',$chomemajor,$chomeminor); + if ($status eq 'fail') { + $blocktypes->{'printout'} = 'off'; + $output .= '

    '. + &mt('Printout blocking not allowed for [_1]', + &escape($env{'form.title_'.$key})).'
    '; + } + } + } + if ($blockdocs) { + ($status,$needsrelease) = &check_release_required('docs',$chomemajor,$chomeminor); + if ($status eq 'fail') { + delete($blocktypes->{'docs'}); + $output .= '

    '. + &mt('Content blocking not allowed for [_1]', + &escape($env{'form.title_'.$key})).'
    '; + } + } $blocking{$newkey} = { setter => $env{'user.name'}.':'.$env{'user.domain'}, event => &escape($env{'form.title_'.$key}), blocks => $blocktypes, }; - if ($blockdocs) { - &check_release_required('docs'); - } - if (ref($blocktypes) eq 'HASH') { - if ($blocktypes->{'printout'} eq 'on') { - &check_release_required('printout'); - } - } if (exists($removals{$key})) { $modtotal ++; } else { @@ -766,9 +810,11 @@ sub blockstore { } } else { if ($env{'form.toggle_'.$key} eq 'timer') { - $output .= '

    '. - &mt('Invalid trigger for new blocking event'). - '

    '; + unless ($status eq 'fail') { + $output .= '

    '. + &mt('Invalid trigger for new blocking event'). + '

    '; + } } else { $output .= '

    '. &mt('No date range found for new blocking event'). @@ -782,9 +828,6 @@ sub blockstore { $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'} ); - if ($addtimer) { - &check_release_required('timer'); - } } $changestotal = $canceltotal + $modtotal + $addtotal; if ($changestotal > 0) { @@ -817,31 +860,21 @@ sub blockstore { return ($changestotal,$output); } -sub enumerate_course_contents { - my ($navmap,$map_url,$resource_symb) = @_; - if ((ref($navmap)) && (ref($map_url) eq 'HASH') && - (ref($resource_symb) eq 'HASH')) { - my $it = $navmap->getIterator(undef,undef,undef,1,undef,undef); - my $count = 0; - while (my $curRes = $it->next()) { - if (ref($curRes)) { - $count ++; - my $symb = $curRes->symb(); - my $ressymb = $symb; - if ($ressymb =~ m|adm/($match_domain)/($match_username)/(\d+)/bulletinboard$|) { - unless ($ressymb =~ m|adm/wrapper/adm|) { - $ressymb = 'bulletin___'.$3.'___adm/wrapper/adm/'.$1.'/'.$2.'/'.$3. - '/bulletinboard'; - } - } - if (($curRes->is_sequence()) || ($curRes->is_page())) { - $map_url->{$count} = (&Apache::lonnet::decode_symb($symb))[2]; - } else { - $resource_symb->{$count} = $ressymb; - } +sub update_releasereq { + my $readmap = 1; + my $getrelreq = 1; + if (ref($modified_courses) eq 'ARRAY') { + foreach my $item (@{$modified_courses}) { + if (ref($item) eq 'ARRAY') { + my ($cdom,$cnum,$chome,$crstype) = @{$item}; + &Apache::lonrelrequtils::modify_course_relreq(undef,undef,$cnum,$cdom, + $chome,$crstype,$cdom.'_'.$cnum, + $readmap,$getrelreq); } } + $modified_courses = []; } + undef($registered_cleanup); return; } @@ -881,6 +914,9 @@ sub get_block_choices { if (ref($symb_ref) eq 'HASH') { my %resources = map { $symb_ref->{$_} => 1; } (split(/,/,$env{'form.docs_resources_'.$item})); + if (exists($resources{''})) { + delete($resources{''}); + } $blocklist->{$type}->{resources} = \%resources; if (keys(%resources) > 0) { $blockdocs = 1; @@ -892,6 +928,9 @@ sub get_block_choices { if (ref($map_ref) eq 'HASH') { my %maps = map { $map_ref->{$_} => 1; } (split(/,/,$env{'form.docs_maps_'.$item})); + if (exists($maps{''})) { + delete($maps{''}); + } $blocklist->{$type}->{maps} = \%maps; if (keys(%maps) > 0) { $blockdocs = 1; @@ -911,10 +950,15 @@ sub get_block_choices { } sub check_release_required { - my ($value) = @_; - my $needsrelease = $Apache::lonnet::needsrelease{'course:commblock:'.$value}; + my ($value,$chomemajor,$chomeminor) = @_; + my $needsrelease = $Apache::lonnet::needsrelease{'course:commblock:'.$value.':'}; if ($needsrelease) { - my $curr_required = + my ($needsmajor,$needsminor) = split(/\./,$needsrelease); + if (($chomemajor < $needsmajor) || + (($chomemajor == $needsmajor) && ($chomeminor < $needsminor))) { + return ('fail',$needsrelease); + } + my $curr_required = $env{'course.'.$env{'request.course.id'}.'.internal.releaserequired'}; if ($curr_required eq '') { &Apache::lonnet::update_released_required($needsrelease); @@ -927,14 +971,14 @@ sub check_release_required { } } } - return; + return ('ok',$needsrelease); } sub display_blocker_status { my ($r,$records,$ltext,$intervals,$navmap,$errormsg,$blockcount) = @_; my $parmcount = 0; - my (%map_url,%resource_symb,%lookups); - &enumerate_course_contents($navmap,\%map_url,\%resource_symb); + my (%map_url,%resource_symb,%titles,%lookups); + &Apache::loncourserespicker::enumerate_course_contents($navmap,\%map_url,\%resource_symb,\%titles,'examblock'); %{$lookups{'maps'}} = reverse(%map_url); %{$lookups{'resources'}} = reverse(%resource_symb); my %lt = &Apache::lonlocal::texthash( @@ -1010,16 +1054,26 @@ ACT if (&Apache::lonnet::is_on_map($url)) { if ($type eq 'map') { if (ref($navmap)) { - my $res = $navmap->getResourceByUrl($item); - my $title = $res->compTitle(); + my $title; + my $resobj = $navmap->getResourceByUrl($item); + if (ref($resobj)) { + $title = $resobj->compTitle(); + } else { + $title = &Apache::lonnet::gettitle($item); + } $itemname = &mt('Timer for all items in folder: [_1]', ''. $title.''); } } else { if (ref($navmap)) { - my $res = $navmap->getBySymb($item); - my $title = $res->compTitle(); + my $title; + my $resobj = $navmap->getBySymb($item); + if (ref($resobj)) { + $title = $resobj->compTitle(); + } else { + $title = &Apache::lonnet::gettitle($item); + } $itemname = &mt('Timer for resource: [_1]', ''. $title.''); @@ -1128,49 +1182,8 @@ END return; } -sub path_to_trigger { - my ($navmap,$map,$type) = @_; - my @pathitems; - if (ref($navmap)) { - my $mapres = $navmap->getResourceByUrl($map); - if (ref($mapres)) { - my $pcslist = $mapres->map_hierarchy(); - if ($pcslist ne '') { - my @pcs = split(/,/,$pcslist); - foreach my $pc (@pcs) { - if ($pc == 1) { - push(@pathitems,&mt('Main Course Documents')); - } else { - my $res = $navmap->getByMapPc($pc); - if (ref($res)) { - my $title = $res->compTitle(); - $title =~ s/\W+/_/g; - if ($title ne '') { - push(@pathitems,$title); - } - } - } - } - } - } - if ($type eq 'resource') { - if ($mapres->{ID} eq '0.0') { - push(@pathitems,&mt('Main Course Documents')); - } else { - my $maptitle = $mapres->compTitle(); - $maptitle =~ s/\W+/_/g; - if ($maptitle ne '') { - push(@pathitems,$maptitle); - } - } - } - } - return @pathitems; -} - sub convlim { my ($timelimit) = @_; - my $output; my @order = ('days','hours','minutes','seconds'); my %catlimits = ( days => 86400, @@ -1180,13 +1193,15 @@ sub convlim { my @toshow; foreach my $cat (@order) { if ($cat eq 'seconds') { - last if ($timelimit <= 0); + if ($timelimit > 0) { + push(@toshow,&mt("[_1] $cat",$timelimit)); + } } elsif ($timelimit >= $catlimits{$cat}) { my $val = int($timelimit/$catlimits{$cat}); if ($val > 0) { push(@toshow,&mt("[_1] $cat",$val)); } - $timelimit =- $val*$catlimits{$cat}; + $timelimit -= $val*$catlimits{$cat}; } } my $output = join(', ',@toshow); @@ -1360,6 +1375,8 @@ sub create_interval_form { if (ref($intervals->{$type}) eq 'HASH') { if (ref($navmap)) { foreach my $map (sort(keys(%{$intervals->{$type}}))) { + next if ((!&Apache::lonnet::is_on_map($map)) && + ($currkey ne $map)); my ($checked,$clickaction); if ($currkey eq $map) { $checked = ' checked="checked"'; @@ -1370,12 +1387,16 @@ sub create_interval_form { '" value="'.&HTML::Entities::encode($map,'"<>&').'"'. $checked.$clickaction.' />'; if ($currkey eq $map) { - $intervalform .= $itemname.''; + $intervalform .= $itemname.''.$iteminfo; } else { - my $res = $navmap->getResourceByUrl($map); - my $title = $res->compTitle(); - my $path; - my $hierarchy = &show_timer_path($type,$map,$navmap); + my ($resobj,$title,$path,$hierarchy); + $resobj = $navmap->getResourceByUrl($map); + if (ref($resobj)) { + $title = $resobj->compTitle(); + } else { + $title = &Apache::lonnet::gettitle($map); + } + $hierarchy = &show_timer_path($type,$map,$navmap); if ($hierarchy) { $path = ' '. &mt('(in: [_1])',$hierarchy). @@ -1385,9 +1406,6 @@ sub create_interval_form { ''.$title.''). ''.$path; } - if ($currkey eq $map) { - $intervalform .= $iteminfo; - } $intervalform .= '
    '; } } @@ -1396,22 +1414,29 @@ sub create_interval_form { if (ref($intervals->{$type}) eq 'HASH') { if (ref($navmap)) { foreach my $resource (sort(keys(%{$intervals->{$type}}))) { - my ($checked,$clickaction); + my ($checked,$clickaction,$resobj); if ($currkey eq $resource) { $checked = ' checked="checked"'; - } elsif ($jschg) { - $clickaction = ' onclick="'.$jschg.'"'; + } else { + $resobj = $navmap->getBySymb($resource); + next unless(ref($resobj)); + if ($jschg) { + $clickaction = ' onclick="'.$jschg.'"'; + } } $intervalform .= ''; + $intervalform .= $itemname.''.$iteminfo; } else { - my $res = $navmap->getBySymb($resource); - my $title = $res->compTitle(); - my $path; - my $hierarchy = &show_timer_path($type,$resource,$navmap); + my ($title,$path,$hierarchy); + if (ref($resobj)) { + $title = $resobj->compTitle(); + } else { + $title = &Apache::lonnet::gettitle($resource); + } + $hierarchy = &show_timer_path($type,$resource,$navmap); if ($hierarchy) { $path = ' '. &mt('(in: [_1])',$hierarchy). @@ -1421,9 +1446,6 @@ sub create_interval_form { ''. $path; } - if ($currkey eq $resource) { - $intervalform .= $iteminfo; - } $intervalform .= '
    '; } } @@ -1457,10 +1479,12 @@ sub show_timer_path { return unless(ref($navmap)); my @pathitems; if ($type eq 'map') { - @pathitems = &path_to_trigger($navmap,$item,$type); + @pathitems = + &Apache::loncommon::get_folder_hierarchy($navmap,$item); } elsif ($type eq 'resource') { my ($map,$id,$resource) = &Apache::lonnet::decode_symb($item); - @pathitems = &path_to_trigger($navmap,$map,$type); + @pathitems = + &Apache::loncommon::get_folder_hierarchy($navmap,$map,1); } if (@pathitems) { return join(' » ',@pathitems); @@ -1478,8 +1502,9 @@ sub blocktype_text { 'blogs' => 'Blogs', 'docs' => 'Content', 'printout' => 'Printouts', + 'passwd' => 'Change Password', ); - my $typeorder = ['com','chat','boards','port','groups','blogs','printout','docs']; + my $typeorder = ['com','chat','boards','port','groups','blogs','printout','docs','passwd']; return ($typeorder,\%types); }