--- loncom/interface/lonblockingmenu.pm 2011/12/28 22:41:02 1.3 +++ loncom/interface/lonblockingmenu.pm 2012/03/31 14:15:24 1.4 @@ -1,8 +1,8 @@ # The LearningOnline Network with CAPA -# Routines for configuring blocking to collaborative functions, and specific -# resources during an exam +# Routines for configuring blocking of access to collaborative functions, +# and specific resources during an exam # -# $Id: lonblockingmenu.pm,v 1.3 2011/12/28 22:41:02 raeburn Exp $ +# $Id: lonblockingmenu.pm,v 1.4 2012/03/31 14:15:24 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -26,7 +26,7 @@ # # http://www.lon-capa.org/ # -############################################################### +############################################################## ############################################################## =pod @@ -44,24 +44,355 @@ lonblockingmenu provides an interface fo 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. + +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 also 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 assigments. + +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: 2 + $crstype - Container type: Course or Community. + + $blockcount - Total number of blocking events 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 +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 partilcuar 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: 1 - type of constraint (currently: 'docs' or 'timer'). + +Outputs: None + +Side Effects: &update_released_required() called in lonnet, if + needed to update version requirements for course. + + =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 &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 +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 - + +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 @@ -73,6 +404,7 @@ use Apache::lonnet; use Apache::Constants qw(:common :http); use Apache::loncommon(); use Apache::lonhtmlcommon(); +use Apache::lonparmset(); use HTML::Entities(); use Apache::lonlocal; use lib '/home/httpd/lib/perl/'; @@ -108,10 +440,59 @@ sub handler { # -----------------------------Get action and calling context from query string - &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'},['action','caller']); + &Apache::loncommon::get_unprocessed_cgi($ENV{'QUERY_STRING'}, + ['action','caller','block']); -# ----------------------------------------------------------------- Breadcrumbs + 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($crstype,$blockcount); + if ($numchanges > 0) { + $blockcount = &get_blockdates(\%records); + } + } + +# ------------------------------------------------------------------ Breadcrumbs &Apache::lonhtmlcommon::clear_breadcrumbs(); if ($env{'form.caller'} eq 'email') { &Apache::lonhtmlcommon::add_breadcrumb @@ -127,10 +508,11 @@ sub handler { ({href=>'/adm/setblock', text=>'Blocking communication/resource access'}); - $r->print(&Apache::loncommon::start_page('Blocking communication/resource access'). - &Apache::lonhtmlcommon::breadcrumbs('Blocking communication/resource access')); + my $js = &blockingmenu_javascript($blockcount); -# ----------------------------------------------------------- Permissions check + $r->print( + &Apache::loncommon::start_page('Blocking communication/content access',$js). + &Apache::lonhtmlcommon::breadcrumbs('Blocking communication/content access')); my $usertype; my $crstype = &Apache::loncommon::course_type(); @@ -140,63 +522,155 @@ sub handler { $usertype = 'students'; } my $lctype = lc($crstype); - my %lt=&Apache::lonlocal::texthash( - 'cbds' => 'Communication blocking during scheduled exams', - 'desc' => "You can use communication blocking to prevent $usertype enrolled in this $lctype from displaying LON-CAPA messages sent by other $usertype during an online exam. As blocking of communication could potentially interrupt legitimate communication between $usertype who are also both enrolled in a different LON-CAPA course or community, please be careful that you select the correct start and end times for your scheduled exam when setting or modifying these parameters.", - 'mecb' => 'Modify existing communication blocking periods', - 'ncbc' => 'No communication blocks currently saved', - 'stor' => 'Save', + 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 assigments 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( - 'dura' => 'Duration', + 'type' => 'Type', + 'defs' => 'Defined Start/End', + 'trig' => 'Triggered by Activating Timer', 'setb' => 'Set by', 'even' => 'Event', 'blck' => 'Blocked?', - 'actn' => 'Action', 'star' => 'Start', - 'endd' => 'End' + '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') { - &blockstore($r); + $r->print($storeresult); + } else { + $r->print( + $lt{'prev'}. + ''. + '

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

' + ); } - $r->print($lt{'desc'}.'

-
- '); - - $r->print('

'.$lt{'mecb'}.'

'); - my %records = (); - my $blockcount = 0; - my $parmcount = 0; - &get_blockdates(\%records,\$blockcount); +# ------------------------ Choose between modifying existing block or adding new + $r->print(''); if ($blockcount > 0) { - $parmcount = &display_blocker_status($r,\%records,\%ltext); + $r->print(<<"END"); +
+
$lt{'actt'} + + + +
+ + + +
+
+
+'); + &display_blocker_status($r,\%records,\%ltext,\%intervals, + $navmap,$errormsg,$blockcount); } - &display_addblocker_table($r,$parmcount,\%ltext); - my $end_page=&Apache::loncommon::end_page(); $r->print(<<"END");
-
-$end_page 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 = shift; + my ($crstype,$blockcount) = @_; my %lt=&Apache::lonlocal::texthash( 'tfcm' => 'The following changes were made', - 'ncwm' => 'No changes were made.' + 'ncwm' => 'No changes were made.', + 'unna' => 'Unable to retrieve contents of course.', ); my %adds = (); my %removals = (); @@ -204,25 +678,36 @@ sub blockstore { my $modtotal = 0; my $canceltotal = 0; my $addtotal = 0; + my $changestotal = 0; + my $addtimer = 0; my %blocking = (); - $r->print('

'.$lt{'head'}.'

'); - foreach my $envkey (keys(%env)) { - if ($envkey =~ m/^form\.modify_(\d+)$/) { - $adds{$1} = $1; - $removals{$1} = $1; - $modtotal ++; - } elsif ($envkey =~ m/^form\.cancel_(\d+)$/) { - $cancels{$1} = $1; - unless ( defined($removals{$1}) ) { - $removals{$1} = $1; - $canceltotal ++; - } - } elsif ($envkey =~ m/^form\.add_(\d+)$/) { - $adds{$1} = $1; - $addtotal ++; + my (%map_url,%resource_symb,$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); + } + &enumerate_course_contents($navmap,\%map_url,\%resource_symb); foreach my $key (keys(%removals)) { my $hashkey = $env{'form.key_'.$key}; &Apache::lonnet::del('comm_block',["$hashkey"], @@ -232,14 +717,59 @@ sub blockstore { } foreach my $key (keys(%adds)) { unless ( defined($cancels{$key}) ) { - my ($newstart,$newend) = &get_dates_from_form($key); - my $newkey = $newstart.'____'.$newend; - my $blocktypes = &get_block_choices($key); - $blocking{$newkey} = { + my $newkey; + 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})) { + $addtimer ++; + } + } + } + } else { + my ($newstart,$newend) = &get_dates_from_form($key); + $newkey = $newstart.'____'.$newend; + } + if ($newkey ne '') { + my ($blocktypes,$blockdocs) = + &get_block_choices($key,\%map_url,\%resource_symb); + $blocking{$newkey} = { setter => $env{'user.name'}.':'.$env{'user.domain'}, event => &escape($env{'form.title_'.$key}), blocks => $blocktypes, }; + if ($blockdocs) { + &check_release_required('docs'); + } + if (exists($removals{$key})) { + $modtotal ++; + } else { + $addtotal ++; + } + } else { + if ($env{'form.toggle_'.$key} eq 'timer') { + $output .= '

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

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

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

'; + } + } } } if ($addtotal + $modtotal > 0) { @@ -247,24 +777,66 @@ sub blockstore { $env{'course.'.$env{'request.course.id'}.'.domain'}, $env{'course.'.$env{'request.course.id'}.'.num'} ); + if ($addtimer) { + &check_release_required('timer'); + } } - my $chgestotal = $canceltotal + $modtotal + $addtotal; - if ($chgestotal > 0) { - $r->print($lt{'tfcm'}.''; } else { - $r->print($lt{'ncwm'}); + $output .= $lt{'ncwm'}; + } + $output .= '
'; + 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; + } + } + } } - $r->print('
'); return; } @@ -276,145 +848,618 @@ sub get_dates_from_form { } sub get_blockdates { - my ($records,$blockcount) = @_; - $$blockcount = 0; + 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}); + $blockcount = keys(%{$records}); if ((keys(%{$records}))[0] =~ /^error: 2 /) { - $$blockcount = 0; + $blockcount = 0; } + return $blockcount; } sub get_block_choices { - my $item = shift; + my ($item,$map_ref,$symb_ref) = @_; my $blocklist; + my $blockdocs; my ($typeorder,$types) = &blocktype_text(); foreach my $type (@{$typeorder}) { - if ($env{'form.'.$type.'_'.$item}) { - $blocklist->{$type} = 'on'; + 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})); + $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})); + $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) = @_; + my $needsrelease = $Apache::lonnet::needsrelease{'course:commblock:'.$value}; + if ($needsrelease) { + my $curr_required = + $env{'course.'.$env{'request.course.id'}.'.internal.releaserequired'}; + if ($curr_required eq '') { + &Apache::lonnet::update_released_required($needsrelease); } else { - $blocklist->{$type} = 'off'; + 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 $blocklist; + return; } sub display_blocker_status { - my ($r,$records,$ltext) = @_; + 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); + %{$lookups{'maps'}} = reverse(%map_url); + %{$lookups{'resources'}} = reverse(%resource_symb); my %lt = &Apache::lonlocal::texthash( 'modi' => 'Modify', - 'canc' => 'Cancel', + 'dele' => 'Delete', + 'noch' => 'No change', ); - my ($typeorder,$types) = &blocktype_text(); - $r->print(&Apache::loncommon::start_data_table()); + $r->print('
'. + &Apache::loncommon::start_data_table()); $r->print(<<"END"); - $ltext->{'dura'} - $ltext->{'setb'} + + $ltext->{'type'} $ltext->{'even'} $ltext->{'blck'} - $ltext->{'actn'} END foreach my $record (sort(keys(%{$records}))) { - my $onchange = 'onFocus="javascript:window.document.forms['. - "'blockform'].elements['modify_".$parmcount."'].". - 'checked=true;"'; - my ($start,$end) = split(/____/,$record); - my $startform = &Apache::lonhtmlcommon::date_setter('blockform','startdate_'.$parmcount,$start,$onchange); - my $endform = &Apache::lonhtmlcommon::date_setter('blockform','enddate_'.$parmcount,$end,$onchange); - + 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(<<"END"); - $ltext->{'star'}: $startform
$ltext->{'endd'}:  $endform - $settername - - -END - foreach my $block (@{$typeorder}) { - my $blockstatus = ''; - if ($blocks->{$block} eq 'on') { - $blockstatus = 'checked="checked"'; + $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 $res = $navmap->getResourceByUrl($item); + my $title = $res->compTitle(); + $itemname = &mt('Timer for all items in folder: [_1]', + ''. + $title.''); + } + } else { + if (ref($navmap)) { + my $res = $navmap->getBySymb($item); + my $title = $res->compTitle(); + $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'). + ''; + } } - $r->print('
'); + 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(&Apache::loncommon::end_data_table_row()); + $r->print(''.&blocker_checkboxes($parmcount,$blocks,$jschg,\%lookups).''. + &Apache::loncommon::end_data_table_row()); $parmcount++; } $r->print(<<"END"); -
-
+
END - return $parmcount; + 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, + hours => 3600, + minutes => 60, + ); + my @toshow; + foreach my $cat (@order) { + if ($cat eq 'seconds') { + last if ($timelimit <= 0); + } 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) = @_; + 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['add_".$parmcount."'].". + 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 $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( - 'addb' => 'Add block', 'exam' => 'e.g., Exam 1', - 'addn' => 'Add new communication blocking periods' ); - my ($typeorder,$types) = &blocktype_text(); - $r->print(<<"END"); -

$lt{'addn'}

-END + 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->{'dura'} + $ltext->{'type'} $ltext->{'even'} $lt{'exam'} $ltext->{'blck'} - $ltext->{'actn'} END - $r->print(&Apache::loncommon::start_data_table_row()); + $r->print(&Apache::loncommon::start_data_table_row().''); $r->print(<<"END"); - $ltext->{'star'}: $startform
$ltext->{'endd'}:  $endform - - -END - foreach my $block (@{$typeorder}) { - $r->print('
'); - } - $r->print(<<"END"); +    +
+
+
$ltext->{'chda'} + $ltext->{'star'}: $startform
$ltext->{'endd'}:  $endform
+ $ltext->{'when'}
+ - + END - $r->print(&Apache::loncommon::end_data_table_row()); - $r->print(&Apache::loncommon::end_data_table()); + $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}}))) { + my ($checked,$clickaction); + if ($currkey eq $map) { + $checked = ' checked="checked"'; + } elsif ($jschg) { + $clickaction = ' onclick="'.$jschg.'"'; + } + $intervalform .= ''; + } else { + my $res = $navmap->getResourceByUrl($map); + my $title = $res->compTitle(); + my $path; + my $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; + } + if ($currkey eq $map) { + $intervalform .= $iteminfo; + } + $intervalform .= '
'; + } + } + } + } elsif ($type eq 'resource') { + if (ref($intervals->{$type}) eq 'HASH') { + if (ref($navmap)) { + foreach my $resource (sort(keys(%{$intervals->{$type}}))) { + my ($checked,$clickaction); + if ($currkey eq $resource) { + $checked = ' checked="checked"'; + } elsif ($jschg) { + $clickaction = ' onclick="'.$jschg.'"'; + } + $intervalform .= ''; + } else { + my $res = $navmap->getBySymb($resource); + my $title = $res->compTitle(); + my $path; + my $hierarchy = &show_timer_path($type,$resource,$navmap); + if ($hierarchy) { + $path = ' '. + &mt('(in: [_1])',$hierarchy). + ''; + } + $intervalform .= &mt('Timer for resource: [_1]',''.$title.''). + ''. + $path; + } + if ($currkey eq $resource) { + $intervalform .= $iteminfo; + } + $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 = &path_to_trigger($navmap,$item,$type); + } elsif ($type eq 'resource') { + my ($map,$id,$resource) = &Apache::lonnet::decode_symb($item); + @pathitems = &path_to_trigger($navmap,$map,$type); + } + if (@pathitems) { + return join(' » ',@pathitems); + } return; } @@ -426,11 +1471,93 @@ sub blocktype_text { 'port' => 'Portfolio', 'groups' => 'Groups', 'blogs' => 'Blogs', + 'docs' => 'Content', ); - my $typeorder = ['com','chat','boards','port','groups','blogs']; + my $typeorder = ['com','chat','boards','port','groups','blogs','docs']; 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__