# The LearningOnline Network with CAPA # Routines for configuring blocking of access to collaborative functions, # and specific resources during an exam # # $Id: lonblockingmenu.pm,v 1.14.2.2 2016/01/27 03:05:37 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # ############################################################## ############################################################## =pod =head1 NAME lonblockingmenu - Handler to set/modify exam blocks in a course. =head1 SYNOPSIS lonblockingmenu provides an interface for setting exam blocks in a course. =head1 DESCRIPTION This module is used to configure blocking of access to collaborative tools and/or resources during an exam. =head1 OVERVIEW To support high-stakes testing, LON-CAPA provides Coordinators with the ability to disable communication and collaborative features within the system for the duration of an exam. Features which can be disabled include: (a) those which a student could use to communicate with another student. Messaging, discussion, chat, blogs, and some functionality in groups fall 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 take place in a lab setting where use of tools outside LON-CAPA, and use of web sites beyond LON-CAPA are unavailable. Access to specified folder(s) and/or resources in the course contents can be restricted for the duration of an exam. Exam blocks are of two types: (a) Blocks with a defined start and end date. (b) Blocks associated with a timed interval set for a specific folder, or resource. When a student attempts to use a collaboration or communication feature which is currently blocked, information will be available about the duration of the block, and the identity of the Course Coordinator who set the block. Although LON-CAPA communication can be blocked during an exam, course personnel with the 'evb' (evade blocking) privilege will continue to receive LON-CAPA messages sent from students in a course with an active block on messaging. Students will not be able to view messages sent by other students in the same course for the duration of the blocking event. Because students may be enrolled in more than one LON-CAPA course at a time it is important to use reasonable time windows for blocking events, or, in 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 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 new blocks, or changes to existing blocks, to propagate to other servers. Changes to existing blocks on the server hosting your current session are available immediately, as cached data on blocks is devalidated automatically on the current server whenever a change is made to a block (including deletion), or when a new block is added. =head1 INTERNAL SUBROUTINES =over =item &get_timed_items() Provides perl data structure with information about timed interval parameters set in a course. Inputs: 2 (optional) $cdom - course's domain $cnum - course's ID Output: 1 Hash nested hashes containing information about timed interval parameters in course). Top level keys are type: course, map, resource. Next inner keys are map or symb. Next inner keys are scope (all, section, group, users). Values are interval (in seconds). =item &blockstore() Stores changes to exam blocks in comm_block.db file for course. Processes deletions, modifications and additions. 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 &get_dates_from_form() Extract start and end dates from web form input for blocks with defined start/end time. Inputs: 1 - $item - numeric ID of current block. Outputs: 2 - $startdate, $enddate (UNIX times for start and end times for blocks with defined start/end =item &get_blockdates() Retrieves contents of comm_block.db file for a course. Inputs: 1 - $records - reference to hash to contain blocks Outputs: 1 - $blockcount - number of blocks 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 particular event, and also which content areas will have access blocked for the duration of the block. Inputs: 3 - $item - numeric ID of current block - $map_ref - reference to hash mapping numeric IDs to map urls - $symb_ref - reference to hash mapping numeric IDs to symbs Outputs: 2 - $blocktypes - reference to hash of features to be blocked - $blockdocs - boolean - 0 if no blocking of content, 1 if blocking of content access =item &check_release_required() Update LON-CAPA version requirements for course if blocked items (content) or blocking type (triggered by student starting timer) require specific LON-CAPA version (i.e., 2.11). 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: 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 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() Generates web form elements used to display, cancel, or modify existing blocking events. Inputs: 7 - $r - Apache request object - $records - Reference to hash of current blocks - $ltext - Reference to hash of phrases (localized) - $intervals - Reference to hash of parameters for timed intervals - $navmap - navmaps object. - $errormsg - error message for display, if navmaps object could not be instantiated - $blockcount - number of existing blocking events in course Output: None Side Effects: prints web form elements (in a table) for current blocks. =item &convlim() Convert a time interval used for a timed quiz (in seconds) to days, hours. minutes and seconds. Inputs: 1 - $timelimit - time interval in seconds Outputs: 1 - $output - time in format: DD days, HH hours, MM minutes, SS seconds =item &display_addblocker_table() Generate web form elements used to define a new blocking event. Inputs: 6 - $r - Apache resource object - $parmcount - current ID for block (same as number of current blocks, block IDs in web form have zero-based index) - $ltext - reference to hash of phrases (localized) - $intervals - Reference to hash of parameters for timed intervals - $navmap - navmaps object - $errormsg - error message for display, if navmaps object could not be instantiated Outputs: None Side Effects: prints web form elements (in a table) for adding a new block. =item &blocker_checkboxes() Generates web form elements in a table for checkboxes used to indicate which types of communication/collaboration and/or content should be blocked. Inputs: 4 - $parmcount - numeric ID of current block - $blocks - reference to hash of functionalities to block - $jschg - text of javascript call to execute when checkbox clicked use within a box via 'onclick="$jchg"' - $lookups - reference to hash to map urls or symbs to numeric IDs used to populate hodden form elements containing list of resources and folders with access blocking currently set. Output: 1 - HTML for table of checkboxes for current block =item &create_interval_form() Creates web form elements used to select one of the defined timed interval items in the course for use in an exam block of type: "Triggered by Activating Timer". Inputs: 7 (three required, last four optional) - $intervals - Reference to hash of parameters for timed intervals - $parmcount - numeric ID of current block - $navmap - navmaps object - $currkey - current interval (where this is a block already using an interval-based trigger). - $jschg - text of javascript call to execute when radiobutton clicked use within a box via 'onclick="$jchg"' - $itemname - name/scope of current interval used for this block - $iteminfo - Expandable/collapsible block showing which users are able to activate the timer using the current trigger item. Outputs: 1 - $intervalform - web form elements used to select a time interval =item &trigger_details_toggle() Creates link used to expand item showing information about timer for current trigger for exam block. Inputs: 1 - $parmcount - numericID of exam block in web form. Outputs: 1 - returns HTML for link to display contents of information item =item &show_timer_path() Display hierarchy of names of folders/sub-folders containing the current item identified as an item with an interval timer set. Inputs: 3 - $type - map or resource - $item - map URL or resource symb - $navmap - navmaps object Outputs: 1 - HTML containing hierarchy of folders/subfolders (raquo entity separated). =item &blocktype_text() Inputs: None Output: 2 - $typeorder - reference to array of blockable communication/collaboration/content - $types -reference to hash of descriptions (localized) of blockable types. =item &blockingmenu_javascript() 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 - 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 access could be blocked. (b) toggle visibility of a number of divs: =over =item * for block type - defined dates or timer activated =item * for action to take -- add or modify block =item * for display of detailed information about intervals =back =back =cut package Apache::lonblockingmenu; use strict; use Apache::lonnet; 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; # ----------------------------------------------------------- Set document type &Apache::loncommon::content_type($r,'text/html'); $r->send_http_header; return OK if $r->header_only; # Needs to be in a course if (! ($env{'request.course.fn'})) { # Not in a course $env{'user.error.msg'}= "/adm/setblock:dcm:0:0:Cannot set blocking of communications in a course"; return HTTP_NOT_ACCEPTABLE; } # ----------------------------------------------------------- Permissions check unless ((&Apache::lonnet::allowed('dcm',$env{'request.course.id'})) || (&Apache::lonnet::allowed('dcm',$env{'request.course.id'}. '/'.$env{'request.course.sec'}))) { $env{'user.error.msg'}= "/adm/setblock:dcm:0:0:Cannot set blocking of communications in a course"; return HTTP_NOT_ACCEPTABLE; } # -----------------------------Get action and calling context from query string $registered_cleanup=0; @{$modified_courses}=(); &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, ['action','caller','block']); my $crstype = &Apache::loncommon::course_type(); my $action = $env{'form.action'}; my %records = (); my $blockcount = 0; # ------------------------------------------------------ Retrieve current blocks $blockcount = &get_blockdates(\%records); # -------------------- Generate display for pop-up of Maps and Resources blocked if ($action eq 'showdocs') { my ($navmap,$errormsg) = &Apache::loncourserespicker::get_navmap_object($crstype,'examblock'); if (ref($navmap)) { my (%blockedmaps,%blockedresources); if ($env{'form.block'} =~ /^\d+$/) { my @currblocks = sort(keys(%records)); my $block = $currblocks[$env{'form.block'}]; if (($block ne '') && (ref($records{$block}) eq 'HASH')) { if (ref($records{$block}{'blocks'}) eq 'HASH') { if (ref($records{$block}{'blocks'}{'docs'}) eq 'HASH') { if (ref($records{$block}{'blocks'}{'docs'}{'maps'}) eq 'HASH') { %blockedmaps = %{$records{$block}{'blocks'}{'docs'}{'maps'}}; } if (ref($records{$block}{'blocks'}{'docs'}{'resources'}) eq 'HASH') { %blockedresources = %{$records{$block}{'blocks'}{'docs'}{'resources'}}; } } } } } $r->print(&Apache::loncourserespicker::create_picker($navmap, 'examblock','resourceblocks',$crstype, \%blockedmaps,\%blockedresources, $env{'form.block'})); } else { $r->print($errormsg); } return OK; } # -------------------------- Store changes and retrieve latest block information my $storeresult; if ($env{'form.action'} eq 'store') { (my $numchanges,$storeresult) = &blockstore($r,$crstype,$blockcount,\%records); if ($numchanges > 0) { $blockcount = &get_blockdates(\%records); } } # ------------------------------------------------------------------ Breadcrumbs &Apache::lonhtmlcommon::clear_breadcrumbs(); if ($env{'form.caller'} eq 'email') { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/communicate', text=>'Communication/Messages', faq=>12,bug=>'Communication Tools',}); } else { &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/parmset', text=>'Content and Problem Settings'}); } &Apache::lonhtmlcommon::add_breadcrumb ({href=>'/adm/setblock', text=>'Blocking communication/content access'}); my $js = &blockingmenu_javascript($blockcount); $r->print( &Apache::loncommon::start_page('Blocking communication/content access',$js). &Apache::lonhtmlcommon::breadcrumbs('Blocking communication/content access')); my $usertype; if ($crstype eq 'Community') { $usertype = 'members'; } else { $usertype = 'students'; } my $lctype = lc($crstype); my %lt=&Apache::lonlocal::texthash ( '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 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)', 'ncbc' => 'There are no blocking events currently saved.', 'stor' => 'Save', ); my %ltext = &Apache::lonlocal::texthash( 'type' => 'Type', 'defs' => 'Defined Start/End', 'trig' => 'Triggered by Activating Timer', 'setb' => 'Set by', 'even' => 'Event', 'blck' => 'Blocked?', 'star' => 'Start', 'endd' => 'End', 'chda' => 'Choose dates', 'chtr' => 'Choose trigger', 'when' => 'When using defined start/end times for an event, please set dates carefully.', 'yes' => 'Yes', 'no' => 'No', ); $r->print('

'.$lt{'cbds'}.'

'); # ---------------------------------------------------- Get Time Limit parameters my %intervals = &get_timed_items(); # -------------------------------------------- Display information about changes if ($env{'form.action'} eq 'store') { $r->print($storeresult); } else { $r->print( $lt{'prev'}. ''. '

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

' ); } # ------------------------ Choose between modifying existing block or adding new $r->print('
'); if ($blockcount > 0) { $r->print(<<"END");
$lt{'actt'}

'); &display_blocker_status($r,\%records,\%ltext,\%intervals, $navmap,$errormsg,$blockcount); } $r->print(<<"END");
END $r->print(&Apache::loncommon::end_page()); return OK; } sub get_timed_items { my ($cdom,$cnum) = @_; my ($cid,%intervals); if ($cdom eq '' || $cnum eq '') { $cid = $env{'request.course.id'}; $cdom = $env{'course.'.$cid.'.domain'}; $cnum = $env{'course.'.$cid.'.num'}; } else { $cid = $cdom.'_'.$cnum; } if ($cid eq '') { return %intervals; } my $resourcedata=&Apache::lonparmset::readdata($cnum,$cdom); if (ref($resourcedata) eq 'HASH') { foreach my $key (keys(%{$resourcedata})) { if ($key =~ /^\Q$cid\E(.+)\.0\.interval$/) { my $middle = $1; if ($middle eq '') { $intervals{'course'}{'all'} = $resourcedata->{$key}; } elsif ($middle =~ /^\.\[(\w+)\]$/) { $intervals{'course'}{'secgrp'}{$1} = $resourcedata->{$key}; } elsif ($middle =~ /^\.\[useropt\:($match_username\:$match_domain)\]$/) { $intervals{'course'}{'users'}{$1} = $resourcedata->{$key}; } elsif ($middle =~ /^\.(.+)\Q___(all)\E$/) { my $inner = $1; if ($inner =~ /^\[(\w+)\]\.([^\]]+)$/) { $intervals{'map'}{$2}{'secgrp'}{$1} = $resourcedata->{$key}; } elsif ($inner =~ /^\[useropt\:($match_username\:$match_domain)\]\.([^\]]+)$/) { $intervals{'map'}{$2}{'users'}{$1} = $resourcedata->{$key}; } else { $intervals{'map'}{$inner}{'all'} = $resourcedata->{$key}; } } elsif ($middle =~ /^\.\[(\w+)\]\.([^\]]+)$/) { $intervals{'resource'}{$2}{'secgrp'}{$1} = $resourcedata->{$key}; } elsif ($middle =~ /^\.\[useropt\:($match_username\:$match_domain)\]\.([^\]]+)$/) { $intervals{'resource'}{$2}{'users'}{$1} = $resourcedata->{$key}; } else { my ($symb) = ($middle =~ /^\.(.+)$/); $intervals{'resource'}{$symb}{'all'} = $resourcedata->{$key}; } } } } return %intervals; } sub blockstore { my ($r,$crstype,$blockcount,$currblockrecs) = @_; my %lt=&Apache::lonlocal::texthash( 'tfcm' => 'The following changes were made', 'ncwm' => 'No changes were made.', 'unna' => 'Unable to retrieve contents of course.', ); my %adds = (); my %removals = (); my %cancels = (); my $modtotal = 0; my $canceltotal = 0; my $addtotal = 0; my $changestotal = 0; my $addtimer = 0; my %blocking = (); my (%map_url,%resource_symb,%titles,$output); $output = '

'.$lt{'head'}.'

'; if ($env{'form.blockaction'} eq 'modify') { foreach my $envkey (keys(%env)) { if ($envkey =~ m/^form\.action_(\d+)$/) { if ($env{$envkey} eq 'modify') { $adds{$1} = 1; $removals{$1} = 1; } elsif ($env{$envkey} eq 'cancel') { $cancels{$1} = $1; unless ( defined($removals{$1}) ) { $removals{$1} = 1; $canceltotal ++; } } } } } elsif ($env{'form.blockaction'} eq 'add') { $adds{$blockcount} = 1; } my ($navmap,$errormsg) = &Apache::loncourserespicker::get_navmap_object($crstype,'examblock'); unless (ref($navmap)) { $output = $lt{'unna'}.' '.$lt{'ncwm'}.'
'; return ($changestotal,$output); } &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}; 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,$status,$needsrelease);; if ($env{'form.firstaccess_'.$key}) { my $interval = &HTML::Entities::decode($env{'form.firstaccess_'.$key}); if ($interval ne '') { if ($interval eq 'course') { $newkey = 'firstaccess____'.$interval; } elsif ($interval =~ /___\d+___/) { my ($map,$resid,$url) = &Apache::lonnet::decode_symb($interval); if (&Apache::lonnet::is_on_map($url)) { $newkey = 'firstaccess____'.$interval; } } elsif (&Apache::lonnet::is_on_map($interval)) { $newkey = 'firstaccess____'.$interval; } if ($newkey ne '') { unless (defined($removals{$key})) { ($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})).'
'; } } } } } else { 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 (exists($removals{$key})) { $modtotal ++; } else { $addtotal ++; } } else { if ($env{'form.toggle_'.$key} eq 'timer') { unless ($status eq 'fail') { $output .= '

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

'; } } else { $output .= '

'. &mt('No date range found for new blocking event'). '

'; } } } } if ($addtotal + $modtotal > 0) { &Apache::lonnet::put('comm_block',\%blocking, $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'} ); } $changestotal = $canceltotal + $modtotal + $addtotal; if ($changestotal > 0) { &Apache::lonnet::devalidate_cache_new('comm_block', $env{'request.course.id'}); $output .= $lt{'tfcm'}.''; } else { $output .= $lt{'ncwm'}; } $output .= '
'; return ($changestotal,$output); } 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; } sub get_dates_from_form { my $item = shift; my $startdate = &Apache::lonhtmlcommon::get_date_from_form('startdate_'.$item); my $enddate = &Apache::lonhtmlcommon::get_date_from_form('enddate_'.$item); return ($startdate,$enddate); } sub get_blockdates { my ($records) = @_; my $blockcount = 0; %{$records} = &Apache::lonnet::dump('comm_block', $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'} ); $blockcount = keys(%{$records}); if ((keys(%{$records}))[0] =~ /^error: 2 /) { $blockcount = 0; } return $blockcount; } sub get_block_choices { my ($item,$map_ref,$symb_ref) = @_; my $blocklist; my $blockdocs; my ($typeorder,$types) = &blocktype_text(); foreach my $type (@{$typeorder}) { if ($type eq 'docs') { if ($env{'form.'.$type.'_'.$item}) { $blocklist->{$type} = {}; if ($env{'form.docs_resources_'.$item}) { $env{'form.docs_resources_'.$item} =~ s/,$//; 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; } } } if ($env{'form.docs_maps_'.$item}) { $env{'form.docs_maps_'.$item} =~ s/,$//; 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; } } } } } else { if ($env{'form.'.$type.'_'.$item}) { $blocklist->{$type} = 'on'; } else { $blocklist->{$type} = 'off'; } } } return ($blocklist,$blockdocs); } sub check_release_required { my ($value,$chomemajor,$chomeminor) = @_; my $needsrelease = $Apache::lonnet::needsrelease{'course:commblock:'.$value}; if ($needsrelease) { 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); } else { my ($currmajor,$currminor) = split(/\./,$curr_required); my ($needsmajor,$needsminor) = split(/\./,$needsrelease); if (($currmajor < $needsmajor) || ($currmajor == $needsmajor && $currminor < $needsminor)) { &Apache::lonnet::update_released_required($needsrelease); } } } return ('ok',$needsrelease); } sub display_blocker_status { my ($r,$records,$ltext,$intervals,$navmap,$errormsg,$blockcount) = @_; my $parmcount = 0; 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( 'modi' => 'Modify', 'dele' => 'Delete', 'noch' => 'No change', ); $r->print('
'. &Apache::loncommon::start_data_table()); $r->print(<<"END"); $ltext->{'type'} $ltext->{'even'} $ltext->{'blck'} END foreach my $record (sort(keys(%{$records}))) { my $jschg = 'javascript:window.document.forms['. "'blockform'".']'. '.elements['."'action_$parmcount'".'][0].checked=true;'; my $onchange = 'onfocus="'.$jschg.'"'; my ($setuname,$setudom,$title,$blocks) = &Apache::loncommon::parse_block_record($$records{$record}); $title = &HTML::Entities::encode($title,'"<>&'); my $blockid = &HTML::Entities::encode($record,'"<>&'); my $settername = &Apache::loncommon::aboutmewrapper( &Apache::loncommon::plainname($setuname,$setudom), $setuname,$setudom); $r->print(&Apache::loncommon::start_data_table_row()); $r->print(<<"ACT");

ACT my ($start,$end,$startform,$endform); if ($record =~ /^(\d+)____(\d+)$/) { ($start,$end) = split(/____/,$record); $startform = &Apache::lonhtmlcommon::date_setter('blockform','startdate_'. $parmcount,$start,$onchange); $endform = &Apache::lonhtmlcommon::date_setter('blockform','enddate_'. $parmcount,$end,$onchange); $r->print('
'.$ltext->{'defs'}.''. $ltext->{'star'}.': '.$startform.'
'. $ltext->{'endd'}.':  '.$endform.'
'); } elsif ($record =~ /^firstaccess____(.+)$/) { my $item = $1; my ($itemname,$iteminfo,$skipdetails); my $type = 'map'; my $url; if ($item eq 'course') { $type = 'course'; } elsif ($item =~ /___\d+___/) { $type = 'resource'; (my $map, my $resid, $url) = &Apache::lonnet::decode_symb($item); } else { $url = $item; } $r->print('
'.$ltext->{'trig'}.''); if ($type eq 'course') { $itemname = &mt('Timer for all items in course.'); } else { if (&Apache::lonnet::is_on_map($url)) { if ($type eq 'map') { if (ref($navmap)) { 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 $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.''); } } if (ref($navmap)) { my $path = &show_timer_path($type,$item); if ($path) { $iteminfo = ' '. &mt('(in: [_1])',$path). ''; } } } else { $skipdetails = 1; $itemname = ''. &mt('Timer folder/resource not in course'). ''; } } if ((!$skipdetails) && (ref($intervals) eq 'HASH')) { if (ref($intervals->{$type}) eq 'HASH') { $iteminfo .= &trigger_details_toggle($parmcount). ''; } } $r->print(&create_interval_form($intervals,$parmcount,$navmap,$item,$jschg, $itemname,$iteminfo).'
'); } $r->print(<<"END");

$ltext->{'setb'}: $settername END $r->print(''.&blocker_checkboxes($parmcount,$blocks,$jschg,\%lookups).''. &Apache::loncommon::end_data_table_row()); $parmcount++; } $r->print(<<"END");
END return; } sub convlim { my ($timelimit) = @_; my @order = ('days','hours','minutes','seconds'); my %catlimits = ( days => 86400, hours => 3600, minutes => 60, ); my @toshow; foreach my $cat (@order) { if ($cat eq 'seconds') { 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}; } } my $output = join(', ',@toshow); return $output; } sub display_addblocker_table { my ($r,$parmcount,$ltext,$intervals,$navmap,$errormsg) = @_; return unless ((ref($ltext) eq 'HASH') && (ref($intervals) eq 'HASH')); my $start = time; my $end = $start + (60 * 60 * 2); #Default is an exam of 2 hours duration. my $onchange = 'onfocus="javascript:window.document.forms['. "'blockform'].elements['addaction'].". 'checked=true;"'; my $startform = &Apache::lonhtmlcommon::date_setter('blockform','startdate_'. $parmcount,$start,$onchange); my $endform = &Apache::lonhtmlcommon::date_setter('blockform','enddate_'. $parmcount,$end,$onchange); my %lt = &Apache::lonlocal::texthash( 'exam' => 'e.g., Exam 1', ); my $intervalform = &create_interval_form($intervals,$parmcount,$navmap); if ($intervalform ne '') { $intervalform = '
'. ''.$ltext->{'chtr'}.''. $intervalform. '
'; } $r->print(&Apache::loncommon::start_data_table()); $r->print(<<"END"); $ltext->{'type'} $ltext->{'even'} $lt{'exam'} $ltext->{'blck'} END $r->print(&Apache::loncommon::start_data_table_row().''); $r->print(<<"END");   
$ltext->{'chda'} $ltext->{'star'}: $startform
$ltext->{'endd'}:  $endform
$ltext->{'when'}
END $r->print(''.&blocker_checkboxes($parmcount).''. &Apache::loncommon::end_data_table_row(). &Apache::loncommon::end_data_table()."\n". '
'); return; } sub blocker_checkboxes { my ($parmcount,$blocks,$jschg,$lookups) = @_; my ($typeorder,$types) = &blocktype_text(); my $numinrow = 2; my %currdocs; my $output = ''; for (my $i=0; $i<@{$typeorder}; $i++) { my $block = $typeorder->[$i]; my ($clickaction,$blockstatus); if ($jschg) { $clickaction = $jschg; } if ($block eq 'docs') { if ((ref($blocks) eq 'HASH') && (ref($lookups) eq 'HASH')) { if (ref($blocks->{$block}) eq 'HASH') { if (keys(%{$blocks->{$block}}) > 0) { $blockstatus = 'checked="checked"'; foreach my $key (sort(keys(%{$blocks->{$block}}))) { if (ref($blocks->{$block}{$key}) eq 'HASH') { my @current = (); foreach my $item (keys(%{$blocks->{$block}{$key}})) { if ($lookups->{$key}{$item}) { push(@current,$lookups->{$key}{$item}); } } if (@current > 0) { @current=sort { $a <=> $b } (@current); $currdocs{$key} = join(',',@current); } } } } } } $clickaction .= 'javascript:resblockinfo('."'$parmcount'".');'; } else { if (ref($blocks) eq 'HASH') { if ($blocks->{$block} eq 'on') { $blockstatus = 'checked="checked"'; } } } my $rem = $i%($numinrow); if ($rem == 0) { if ($i > 0) { $output .= ''; } $output .= ''; } if ($i == scalar(@{$typeorder})-1) { my $colsleft = $numinrow-$rem; if ($colsleft > 1) { $output .= ''; } $output .= '
'; } else { $output .= ''; } } else { $output .= ''; } my $item = $block.'_'.$parmcount; if ($clickaction) { $clickaction = ' onclick="'.$clickaction.'"'; } if ($blockstatus) { $blockstatus = ' '.$blockstatus; } $output .= ''."\n"; if ($block eq 'docs') { if ($blockstatus ne '') { $output .= ' '. &mt('Details').''; } } $output .= '
'. ''. ''; return $output; } sub create_interval_form { my ($intervals,$parmcount,$navmap,$currkey,$jschg,$itemname,$iteminfo) = @_; return unless ((ref($intervals) eq 'HASH') && (ref($navmap))); my $intervalform; if (keys(%{$intervals}) > 0) { foreach my $type (sort(keys(%{$intervals}))) { if ($type eq 'course') { my ($checked,$clickaction); if ($currkey eq 'course') { $checked = ' checked="checked"'; } elsif ($jschg) { $clickaction = ' onclick="'.$jschg.'"'; } $intervalform .= ''; if ($currkey eq 'course') { $intervalform .= $iteminfo; } $intervalform .= '
'; } elsif ($type eq 'map') { 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"'; } elsif ($jschg) { $clickaction = ' onclick="'.$jschg.'"'; } $intervalform .= ''.$iteminfo; } else { 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). ''; } $intervalform .= &mt('Timer for all items in folder: [_1]', ''.$title.''). ''.$path; } $intervalform .= '
'; } } } } elsif ($type eq 'resource') { if (ref($intervals->{$type}) eq 'HASH') { if (ref($navmap)) { foreach my $resource (sort(keys(%{$intervals->{$type}}))) { my ($checked,$clickaction,$resobj); if ($currkey eq $resource) { $checked = ' checked="checked"'; } else { $resobj = $navmap->getBySymb($resource); next unless(ref($resobj)); if ($jschg) { $clickaction = ' onclick="'.$jschg.'"'; } } $intervalform .= ''.$iteminfo; } else { 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). ''; } $intervalform .= &mt('Timer for resource: [_1]',''.$title.''). ''. $path; } $intervalform .= '
'; } } } } } } else { if ($currkey ne '') { $intervalform = '&').' />'. $itemname.'
'; } else { $intervalform = &mt('No timed items defined.').' '. &mt('Use [_1]Settings[_2] to assign a timer, then return here.', '',''); } } return $intervalform; } sub trigger_details_toggle { my ($parmcount) = @_; return ' '. ''.&mt('(More ...)').''; } sub show_timer_path { my ($type,$item,$navmap) = @_; return unless(ref($navmap)); my @pathitems; if ($type eq 'map') { @pathitems = &Apache::loncommon::get_folder_hierarchy($navmap,$item); } elsif ($type eq 'resource') { my ($map,$id,$resource) = &Apache::lonnet::decode_symb($item); @pathitems = &Apache::loncommon::get_folder_hierarchy($navmap,$map,1); } if (@pathitems) { return join(' » ',@pathitems); } return; } sub blocktype_text { my %types = &Apache::lonlocal::texthash( 'com' => 'Messaging', 'chat' => 'Chat Room', 'boards' => 'Discussion', 'port' => 'Portfolio', 'groups' => 'Groups', 'blogs' => 'Blogs', 'docs' => 'Content', 'printout' => 'Printouts', 'passwd' => 'Change Password', ); my $typeorder = ['com','chat','boards','port','groups','blogs','printout','docs','passwd']; return ($typeorder,\%types); } sub blockingmenu_javascript { my ($blockcount) = @_; my %lt = &Apache::lonlocal::texthash ( more => 'More ...', less => 'Less ...', ); return < // 0) { for (var i=0; i($lt{'less'})'; return; } function hideTriggerDetails(item) { document.getElementById('trigdetails_'+item).style.display='none'; document.getElementById('toggletext_'+item).innerHTML = '($lt{'more'})'; return; } // ]]> ENDSCRIPT } 1; __END__