File:  [LON-CAPA] / loncom / interface / lonblockingmenu.pm
Revision 1.31: download - view: text, annotated - select for diffs
Fri Dec 20 00:24:38 2024 UTC (5 days, 19 hours ago) by raeburn
Branches: MAIN
CVS tags: version_2_12_X, HEAD
- Report OK when the PerlCleanupHandler has successfully finished execution.

# 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.31 2024/12/20 00:24:38 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_permission()

Returns information about permission user has to set/modify exam
blocking events.

Inputs: None

Outputs: 2
    $readonly - true if modification of blocking events is prohibited.

    $allowed  - true if blocking events information can be shown.


=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: 8 
      - $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

      - $readonly - if true, modification not allowed.


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: 8 (four required, last four optional)
   - $intervals - Reference to hash of parameters for timed intervals

   - $parmcount - numeric ID of current block

   - $navmap - navmaps object

   - $context - this will be "accesstimes" if called by lonaccesstimes.pm,
                or "blocking" if called internally by lonblockingmenu.pm

   - $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 &interval_details()

Creates name/scope of current interval and expandable/collapsible
showing which interval parameters apply to the current folder/resource

Inputs: 6

    - $item - course, map url, or resource symb

    - $type - course, map, or resource

    - $url  - url of item (null if item is course).

    - $navmap - navmaps object

    - $intervals - Reference to hash of parameters for timed intervals

    - $parmcount - unique ID for current element.


Outputs: 2

   - $itemname - name/scope of interval (timer) parameter

   - $iteminfo - Expandable/collapsible block showing which interval
                 (timer) parameters affect the current folder or resource.


=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 <script></script> 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

=back


=item &details_javascript()

Create Javascript to toggle visibility of unordered list item
containing details about item with timed interval parameter.

Inputs: none

Output: 1 Javascript (with <script></script> tags) for functions used to:
          toggle visibility of unordered list for display of detailed
          information about intervals.

=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

    my ($readonly,$allowed) = &get_permission();
    unless ($allowed) {
        $env{'user.error.msg'}=
     "/adm/setblock:dcm:0:0:Cannot view/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'},'','',undef,undef,undef,
                                     $readonly));
        } else {
            $r->print($errormsg);
        }
        return OK;
    }

# -------------------------- Store changes and retrieve latest block information
    my $storeresult;
    unless ($readonly) {
        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).
             &details_javascript();

    $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:",
            'flow' => "For the duration of an exam, or a timed quiz, event-driven interruptions to a student's workflow can be suppressed:",
            '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('<h3>'.$lt{'cbds'}.'</h3>');

# ---------------------------------------------------- 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'}.
            '<ul>'."\n".
            '<li>'.&mt("displaying LON-CAPA messages sent by other $usertype in the $lctype").'</li>'."\n".
            '<li>'.&mt("displaying or posting to LON-CAPA discussion boards or live chat in the $lctype").'</li>'."\n".
            '<li>'.&mt('accessing content in LON-CAPA portfolios, blogs, or user information pages').'</li>'."\n".
            '<li>'.&mt("generating printouts of $lctype content").'</li>'.
            '<li>'.&mt("displaying the LON-CAPA gradebook in the $lctype").'</li>'.
            '<li>'.&mt("searching $lctype content by keyword").'</li>'.  
            '<li>'.&mt("accessing $lctype content in specified folders or resources").'</li>'.
            '<li>'.&mt("changing user's own password").'</li>'.
            '</ul>'.
            $lt{'flow'}.
            '<ul>'."\n".
            '<li>'.&mt("re-initialization of cached course structure, when a change has been made to $lctype content by a Coordinator").'</li>'.
            '<li>'.&mt('display of Critical Messages when navigation arrows used to move to the adjacent resource').'</li>'.
            '</ul>'.
            '<p class="LC_warning">'.$lt{'blca'}.'<br />'.$lt{'pobl'}.'</p>'
        );
    }

# ------------------------ Choose between modifying existing block or adding new
    $r->print('<form name="blockform" method="post" action="/adm/setblock?action=store">');

    unless ($readonly) {
        if ($blockcount > 0) {
            $r->print(<<"END");
<div class="LC_left_float">
<fieldset><legend>$lt{'actt'}</legend>
<span class="LC_nobreak">
<label><input type="radio" name="blockaction" value="modify" id="modifyaction" 
onclick="toggleAddModify();" checked="checked" />$lt{'mexb'}</label>
</span>
<br />
<span class="LC_nobreak">
<label><input type="radio" name="blockaction" value="add" id="addaction" 
onclick="toggleAddModify();" />$lt{'addn'}</label>
</span>
</fieldset>
</div>
<br clear="all" />
<div id="showadd" style="display:none">
END
        } else {
            $r->print($lt{'ncbc'}.'<br /><br />'.
                      '<h4>'.$lt{'addn'}.'</h4>'.
                      '<input type="hidden" name="blockaction" value="add" />');
        }
    }
    my ($navmap,$errormsg) =
        &Apache::loncourserespicker::get_navmap_object($crstype,'examblock');

# --------------------------------------------- Interface for adding a new block
    unless ($readonly) {
        &display_addblocker_table($r,$blockcount,\%ltext,\%intervals,
                                  $navmap,$errormsg);
        if ($blockcount > 0) {
            $r->print('</div>');
        }
    }
# ------------------------------------------------ Interface for existing blocks
    if (!$blockcount) {
        if ($readonly) {
            $r->print($lt{'ncbc'}.'<br />');
        }
    } else {
        &display_blocker_status($r,\%records,\%ltext,\%intervals,
                                $navmap,$errormsg,$blockcount,$readonly);
    }
    unless ($readonly) {
        $r->print(<<"END");
<br />
<input type ="submit" value="$lt{'stor'}" />
END
    }
    $r->print('</form>'.
              &Apache::loncommon::end_page());
    return OK;
}

sub get_permission {
    my %permission;
    my $allowed = 0;
    my $readonly = 0;
    return ($readonly,$allowed) unless ($env{'request.course.id'});
    if ((&Apache::lonnet::allowed('dcm',$env{'request.course.id'})) ||
        (&Apache::lonnet::allowed('dcm',$env{'request.course.id'}.'/'.
                  $env{'request.course.sec'}))) {
        $allowed = 1;
    } elsif ((&Apache::lonnet::allowed('vcb',$env{'request.course.id'})) ||
             (&Apache::lonnet::allowed('vcb',$env{'request.course.id'}.'/'.
                  $env{'request.course.sec'}))) {
        $readonly = 1;
        $allowed = 1;
    }
    return ($readonly,$allowed);
}

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 = '<h3>'.$lt{'head'}.'</h3>';
    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'}.'</br>';
        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 .= '<p class="LC_warning">'.
                                           &mt('Triggering of blocking events not allowed for [_1]',
                                               &escape($env{'form.title_'.$key})).'<br />';
                            }
                        }
                    }
                }
            } 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).'</p>';
            }
            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 .= '<p class="LC_warning">'.
                                       &mt('Printout blocking not allowed for [_1]',
                                           &escape($env{'form.title_'.$key})).'<br />';
                        }
                    }
                    if ($blocktypes->{'alert'} eq 'on') {
                        ($status,$needsrelease) = &check_release_required('alert',$chomemajor,$chomeminor);
                        if ($status eq 'fail') {
                            $blocktypes->{'alert'} = 'off';
                            $output .= '<p class="LC_warning">'.
                                       &mt('Message Alert blocking not allowed for [_1]',
                                           &escape($env{'form.title_'.$key})).'<br />';
                        }
                    }
                    if ($blocktypes->{'reinit'} eq 'on') {
                        ($status,$needsrelease) = &check_release_required('reinit',$chomemajor,$chomeminor);
                        if ($status eq 'fail') {
                            $blocktypes->{'reinit'} = 'off';
                            $output .= '<p class="LC_warning">'.
                                       &mt('Course Re-initialization blocking not allowed for [_1]',
                                           &escape($env{'form.title_'.$key})).'<br />';
                        }
                    }
                }
                if ($blockdocs) {
                    ($status,$needsrelease) = &check_release_required('docs',$chomemajor,$chomeminor);
                    if ($status eq 'fail') {
                        delete($blocktypes->{'docs'});
                        $output .= '<p class="LC_warning">'.
                                   &mt('Content blocking not allowed for [_1]',
                                       &escape($env{'form.title_'.$key})).'<br />';
                    }
                }
                $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 .= '<p class="LC_warning">'.
                                   &mt('Invalid trigger for new blocking event').
                                   '</p>';
                    }
                } else {
                    $output .= '<p class="LC_warning">'.
                               &mt('No date range found for new blocking event').
                               '</p>';
                }
            }
        }
    }
    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'}.'<ul>';
        if ($canceltotal > 0) {
            $output .= '<li>'.
                       &mt('[quant,_1,blocking event was,blocking events were] removed.',
                           $canceltotal).
                       '</li>';
        }
        if ($modtotal > 0) {
            $output .= '<li>'.
                       &mt('[quant,_1,blocking event was,blocking events were] modified.',
                           $modtotal).
                       '</li>';
        }
        if ($addtotal > 0) {
            $output .= '<li>'.
                       &mt('[quant,_1,blocking event was,blocking events were] added.',
                           $addtotal).
                       '</li>';
        }
        $output .= '</ul>';
    } else {
        $output .= $lt{'ncwm'};
    }
    $output .= '<br />';
    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 = [];
    }
    return OK;
}

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,$readonly) = @_;
    my $parmcount = 0;
    my (%map_url,%resource_symb,%titles,%lookups,$disabled);
    &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('<div id="showmodify" style="display:block">'.
              &Apache::loncommon::start_data_table().'<tr>');
    if ($readonly) {
        $disabled = ' disabled="disabled"';
    } else {
        $r->print('<th></th>');
    }
    $r->print(<<"END");
    <th>$ltext->{'type'}</th>
    <th>$ltext->{'even'}</th>
    <th>$ltext->{'blck'}</th>
  </tr>
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);
        my $state = '';
        $r->print(&Apache::loncommon::start_data_table_row());
        if ($readonly) {
            $state = 'disabled';
        } else {
            $r->print(<<"ACT");

        <td valign="middle"><span class="LC_nobreak"><label>
        <input type="radio" name="action_$parmcount" value="modify" />$lt{'modi'}
        </label></span><br />
        <span class="LC_nobreak"><label>
        <input type="radio" name="action_$parmcount" value="cancel" />$lt{'dele'}
        </label></span><br />
        <span class="LC_nobreak"><label>
        <input type="radio" name="action_$parmcount" id="nochange_$parmcount" 
         value="nochange" checked="checked" />$lt{'noch'}
        </label></span>
        </td>
ACT
        }
        my ($start,$end,$startform,$endform); 
        if ($record =~ /^(\d+)____(\d+)$/) {
            ($start,$end) = split(/____/,$record);
            $startform = &Apache::lonhtmlcommon::date_setter('blockform','startdate_'.
                                                             $parmcount,$start,$onchange,
                                                             undef,$state);
            $endform = &Apache::lonhtmlcommon::date_setter('blockform','enddate_'.
                                                           $parmcount,$end,$onchange,
                                                           undef,$state);
            $r->print('<td><fieldset><legend>'.$ltext->{'defs'}.'</legend>'.
                      $ltext->{'star'}.':&nbsp;'.$startform.'<br />'.
                      $ltext->{'endd'}.':&nbsp;&nbsp;'.$endform.'</fieldset></td>');
        } elsif ($record =~ /^firstaccess____(.+)$/) {
            my $item = $1;
            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('<td><fieldset><legend>'.$ltext->{'trig'}.'</legend>');
            my ($itemname,$iteminfo) = &interval_details($item,$type,$url,$navmap,$intervals,$parmcount);
            $r->print(&create_interval_form($intervals,$parmcount,$navmap,'blocking',$item,$jschg,
                                            $itemname,$iteminfo,$disabled).'</fieldset></td>');
        }
        $r->print(<<"END");
        <td>
         <input type="text" name="title_$parmcount" size="15" value="$title" onfocus="$jschg" $disabled />
         <input type="hidden" name="key_$parmcount" value="$blockid" />
         <br />
         <br />
         $ltext->{'setb'}: $settername
        </td>
END
        $r->print('<td>'.&blocker_checkboxes($parmcount,$blocks,$jschg,\%lookups,$disabled).'</td>'.
                  &Apache::loncommon::end_data_table_row());
        $parmcount++;
    }
    $r->print(<<"END");
</table>
</div>
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,'blocking');
    if ($intervalform ne '') {
        $intervalform = '<fieldset>'.
                        '<legend>'.$ltext->{'chtr'}.'</legend>'.
                        $intervalform.
                        '</fieldset>';
    }
    $r->print(&Apache::loncommon::start_data_table());
    $r->print(<<"END");
   <tr>
     <th>$ltext->{'type'}</th>
     <th>$ltext->{'even'} $lt{'exam'}</th>
     <th>$ltext->{'blck'}</th>
   </tr>
END
    $r->print(&Apache::loncommon::start_data_table_row().'<td>');
    $r->print(<<"END");
     <span class="LC_nobreak"><label><input type="radio" id="toggle_startstop" 
     name="toggle_$parmcount" value="startstop" onclick="showBlockType();" checked="checked" />
     $ltext->{'defs'}</label></span>&nbsp;&nbsp; 
     <span class="LC_nobreak"><label><input type="radio" id="toggle_timer" name="toggle_$parmcount" 
     value="timer" onclick="showBlockType();" />$ltext->{'trig'}</label></span><br />
     <div id="show_startstop" style="display:block">
     <fieldset><legend>$ltext->{'chda'}</legend>
     $ltext->{'star'}:&nbsp;$startform<br />$ltext->{'endd'}:&nbsp;&nbsp;$endform</fieldset>
     <span class="LC_warning">$ltext->{'when'}</span></div>
     <div id="show_timer" style="display:none">$intervalform</div>
     </td>
     <td><input type="text" name="title_$parmcount" size="15" value="" /></td>
END
    $r->print('<td>'.&blocker_checkboxes($parmcount).'</td>'.
              &Apache::loncommon::end_data_table_row().
              &Apache::loncommon::end_data_table()."\n".
              '<br />');
    return;
}

sub blocker_checkboxes {
    my ($parmcount,$blocks,$jschg,$lookups,$disabled) = @_;
    my ($typeorder,$types) = &blocktype_text();
    my $numinrow = 2;
    my %currdocs;
    my $output = '<table>';
    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 .= '</tr>';
            }
            $output .= '<tr>';
        }
        if ($i == scalar(@{$typeorder})-1) {
            my $colsleft = $numinrow-$rem;
            if ($colsleft > 1) {
                $output .= '<td colspan="'.$colsleft.'">';
            } else {
                $output .= '<td>';
            }
        } else {
            $output .= '<td>';
        }
        my $item = $block.'_'.$parmcount;
        if ($clickaction) {
            $clickaction = ' onclick="'.$clickaction.'"';
        }
        if ($blockstatus) {
            $blockstatus = ' '.$blockstatus;
        } 
        $output .= '<span class="LC_nobreak"><label>'."\n".
                   '<input type="checkbox" id="'.$item.'" name="'.$item.'"'.
                   $blockstatus.$clickaction.' value="1"'.$disabled.' />'.
                   $types->{$block}.'</label></span>'."\n";
        if ($block eq 'docs') {
            if ($blockstatus ne '') {
                $output .= '&nbsp;<a href="javascript:resblockinfo('."'$parmcount'".')">'.
                            &mt('Details').'</a>';
            }
        }
        $output .= '<br /></td>';
    }
    $output .= '</tr></table>'.
               '<input type="hidden" name="docs_maps_'.$parmcount.'"'.
               ' id="docs_maps_'.$parmcount.'" value="'.$currdocs{'maps'}.'" />'.
               '<input type="hidden" name="docs_resources_'.$parmcount.'"'.
               ' id="docs_resources_'.$parmcount.'" value="'.$currdocs{'resources'}.'" />';
    return $output;
}

sub create_interval_form {
    my ($intervals,$parmcount,$navmap,$context,$currkey,$jschg,$itemname,$iteminfo,$disabled) = @_;
    return unless ((ref($intervals) eq 'HASH') && (ref($navmap)));
    my $intervalform;
    my $counter = 0;
    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 .= '<label><input type="radio" name="firstaccess_'.$parmcount.
                                 '" value="course"'.$checked.$clickaction.$disabled.' />';
                if ($currkey eq 'course') {
                    $intervalform .= $itemname;
                } else {
                    $intervalform .= &mt('Timer for all items in course');
                }
                $intervalform .= '</label>';
                if ($currkey eq 'course') {
                    $intervalform .= $iteminfo;
                } elsif ($context eq 'accesstimes') {
                    (undef,$iteminfo) = &interval_details('course',$type,'',$navmap,$intervals,$counter);
                    if ($iteminfo) {
                        $intervalform .= ' '.$iteminfo;
                    }
                }
                $intervalform .= '<br />';
                $counter ++;
            } 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 .= '<label><input type="radio" name="firstaccess_'.$parmcount.
                                             '" value="'.&HTML::Entities::encode($map,'"<>&').'"'.
                                             $checked.$clickaction.$disabled.' />';
                            if ($currkey eq $map) {
                                $intervalform .= $itemname.'</label>'.$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 = ' <span style="font-size:90%;">'.
                                            &mt('(in: [_1])',$hierarchy).
                                            '</span>';
                                }
                                $intervalform .= &mt('Timer for all items in folder: [_1]',
                                                     '<i>'.$title.'</i>').
                                                 '</label>'.$path;
                                if ($context eq 'accesstimes') {
                                    (undef,$iteminfo) = &interval_details($map,$type,$map,$navmap,$intervals,$counter);
                                    if ($iteminfo) {
                                        $intervalform .= ' '.$iteminfo;
                                    }
                                }
                            }
                            $intervalform .= '<br />';
                            $counter ++;
                        }
                    }
                }
            } 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 .= '<label><input type="radio" name="firstaccess_'.$parmcount.
                                             '" value="'.&HTML::Entities::encode($resource,'"<>&').'"'.
                                             $checked.$clickaction.$disabled.' />';
                            if ($currkey eq $resource) {
                                $intervalform .= $itemname.'</label>'.$iteminfo;
                            } else {
                                my ($title,$path,$hierarchy);
                                if (ref($resobj)) {
                                    $title = $resobj->compTitle();
                                }
                                if ($title eq '') {
                                    $title = &Apache::lonnet::gettitle($resource);
                                }
                                $hierarchy = &show_timer_path($type,$resource,$navmap);
                                if ($hierarchy) {
                                    $path = ' <span style="font-size:90%;">'.
                                            &mt('(in: [_1])',$hierarchy).
                                            '</span>';
                                }
                                $intervalform .= &mt('Timer for resource: [_1]','<i>'.$title.'</i>').
                                                 '</label>'.
                                                 $path;
                                if ($context eq 'accesstimes') {
                                    if (ref($resobj)) {
                                        my $url = $resobj->src();
                                        if ($url eq '') {
                                            (my $map, my $resid, $url) = &Apache::lonnet::decode_symb($resource);
                                        }
                                        ($itemname,$iteminfo) = &interval_details($resource,$type,$url,$navmap,$intervals,$counter);
                                        $intervalform .= ' '.$iteminfo;
                                    }
                                }
                            }
                            $intervalform .= '<br />';
                            $counter ++;
                        }
                    }
                }
            }
        }
    } else {
        if ($currkey ne '') {
            $intervalform = '<input type="radio" name="firstaccess_'.$parmcount.
                            '" checked="checked" value="'.
                            &HTML::Entities::encode($currkey,'"<>&').'"'.$disabled.' />'.
                            $itemname.'<br />';
        } else {
            $intervalform = &mt('No timed items defined.').' '.
                            &mt('Use [_1]Settings[_2] to assign a timer, then return here.',
                                '<a href="/adm/parmset">','</a>');
        }
    }
    return $intervalform;
}

sub trigger_details_toggle {
    my ($parmcount) = @_;
    return ' <span id="toggletext_'.$parmcount.'" class="LC_cusr_subheading LC_nobreak">'.
           '<a href="javascript:showTriggerDetails('."'$parmcount'".');" '.
           'style="text-decoration: none;"><b>'.&mt('(More ...)').'</b></a></span>';
}

sub interval_details {
    my ($item,$type,$url,$navmap,$intervals,$parmcount) = @_;
    my ($itemname,$iteminfo,$skipdetails);
    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]',
                                    '<span style="font-style:italic">'.
                                    $title.'</span>');
                }
            } 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]',
                                    '<span style="font-style:italic">'.
                                    $title.'</span>');
                }
            }
            if (ref($navmap)) {
                my $path = &show_timer_path($type,$item);
                if ($path) {
                   $iteminfo  = ' <span style="font-size:90%;">'.
                                  &mt('(in: [_1])',$path).
                                  '</span>';
                }
            }
        } else {
            $skipdetails = 1;
            $itemname = '<span style="LC_warning">'.
                        &mt('Timer folder/resource not in course').
                        '</span>';
        }
    }
    if ((!$skipdetails) && (ref($intervals) eq 'HASH') && (ref($intervals->{$type}) eq 'HASH')) {
        $iteminfo = &trigger_details_toggle($parmcount).
                    '<ul id="trigdetails_'.$parmcount.'" style="display:none">';
        if ($type eq 'course') {
            foreach my $scope (keys(%{$intervals->{$type}})) {
                if ($scope eq 'all') {
                    $iteminfo .= '<li>'.&mt('All users -- time limit: [_1]',
                                 &convlim($intervals->{$type}->{$scope})).'</li>';
                } elsif ($scope eq 'secgrp') {
                    if (ref($intervals->{$type}->{$scope}) eq 'HASH') {
                        $iteminfo .= '<li>'.&mt('Sections/groups').'<ul>';
                        foreach my $item (sort(keys(%{$intervals->{$type}->{$scope}}))) {
                            $iteminfo .= '<li>'.&mt('[_1] -- time limit: [_2]',$item,
                                         &convlim($intervals->{$type}->{$scope}->{$item})).
                                         '</li>';
                        }
                        $iteminfo .= '</ul></li>';
                    }
                } elsif ($scope eq 'users') {
                    if (ref($intervals->{$type}->{$scope}) eq 'HASH') {
                        $iteminfo .= '<li>'.&mt('Users').'<ul>';
                        foreach my $item (sort(keys(%{$intervals->{$type}->{$scope}}))) {
                            $iteminfo .= '<li>'.&mt('[_1] -- time limit: [_2]',
                                         &convlim($item,$intervals->{$type}->{$scope}->{$item})).
                                         '</li>';
                        }
                        $iteminfo .= '</ul></li>';
                    }
                }
            }
        } elsif (($type eq 'map') || ($type eq 'resource')) {
            if (ref($intervals->{$type}->{$item}) eq 'HASH') {
                foreach my $scope (keys(%{$intervals->{$type}->{$item}})) {
                    if ($scope eq 'all') {
                        $iteminfo .= '<li>'.&mt('All users -- time limit: [_1]',
                                     &convlim($intervals->{$type}->{$item}->{$scope})).
                                     '</li>';
                    } elsif ($scope eq 'secgrp') {
                        if (ref($intervals->{$type}->{$item}->{$scope}) eq 'HASH') {
                            $iteminfo .= '<li>'.&mt('Sections/groups').'<ul>';
                            foreach my $sec (sort(keys(%{$intervals->{$type}->{$item}->{$scope}}))) {
                                $iteminfo .= '<li>'.&mt('[_1] -- time limit: [_2]',$sec,
                                             &convlim($intervals->{$type}->{$item}->{$scope}->{$sec})).
                                            '</li>';
                            }
                            $iteminfo .= '</ul></li>';
                        }
                    } elsif ($scope eq 'users') {
                        if (ref($intervals->{$type}->{$item}->{$scope}) eq 'HASH') {
                            $iteminfo .= '<li>'.&mt('Users').'<ul>';
                            foreach my $user (sort(keys(%{$intervals->{$type}->{$item}->{$scope}}))) {
                                $iteminfo .= '<li>'.&mt('[_1] -- time limit: [_2]',$user,
                                             &convlim($intervals->{$type}->{$item}->{$scope}->{$user})).
                                             '</li>';
                            }
                            $iteminfo .= '</ul></li>';
                        }
                    }
                }
            }
        }
        $iteminfo .= '</ul>';
    }
    return ($itemname,$iteminfo);
}

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(' &raquo; ',@pathitems);
    }
    return;
}

sub blocktype_text {
    my %types = &Apache::lonlocal::texthash(
        'com' => 'Messaging',
        'chat' => 'Chat Room',
        'boards' => 'Discussion',
        'port' => 'Portfolio',
        'groups' => 'Groups',
        'blogs' => 'Blogs',
        'about' => 'User Information',
        'docs' => 'Content',
        'printout' => 'Printouts',
        'passwd' => 'Change Password',
        'grades' => 'Gradebook',
        'search' => 'Content Search',
        'alert'  => 'Critical Alert',
        'reinit' => 'Course Re-init',
    );
    my $typeorder = ['com','chat','boards','port','groups','blogs','about','printout','docs','grades','search','alert','reinit','passwd'];
    return ($typeorder,\%types);
}

sub blockingmenu_javascript {
    my ($blockcount) = @_;
    return <<ENDSCRIPT;
<script type="text/javascript">
// <![CDATA[
function resblockinfo(blockid) {
    if (document.getElementById('docs_'+blockid).checked) {
        var resblockwin = null;
        var url = '/adm/setblock?action=showdocs&block='+blockid;
        if (!resblockwin || resblockwin.closed) {
            resblockwin=window.open(url,'blockingwin','height=480,width=600,resizable=yes,scrollbars=yes,location=no,menubar=no,toolbar=no');
        }
        resblockwin.focus();
    } else {
        document.getElementById('docs_resources_'+blockid).value = '';
        document.getElementById('docs_maps_'+blockid).value = '';
    }
    return;
}

function showBlockType() {
    if (document.getElementById('toggle_startstop').checked == true) {
        document.getElementById('show_startstop').style.display='block';
    } else {
        document.getElementById('show_startstop').style.display='none';
    }
    if (document.getElementById('toggle_timer').checked == true) {
        document.getElementById('show_timer').style.display='block';
    } else {
        document.getElementById('show_timer').style.display='none';
    }
    return;
}

function toggleAddModify() {
    for (var i=0; i<document.blockform.blockaction.length; i++) {
        if (document.blockform.blockaction[i].checked) {
            if (document.blockform.blockaction[i].value == 'add') {
               document.getElementById('showadd').style.display='block';
               document.getElementById('showmodify').style.display='none';
               var blocktotal = $blockcount;
               if (blocktotal > 0) {
                   for (var i=0; i<blocktotal; i++) {
                       document.getElementById('nochange_'+i).checked = true;
                   }
               }
               document.getElementById('showmodify').style.display='none';
               document.getElementById('showadd').style.display='block';
            } else {
               document.getElementById('showadd').style.display='none';
               document.getElementById('showmodify').style.display='block';
            }
        }
    }
    return;
}

// ]]>
</script>
ENDSCRIPT

}

sub details_javascript {
    my %lt = &Apache::lonlocal::texthash (
                                           more => 'More ...',
                                           less => 'Less ...',
                                         );
    return <<ENDSCRIPT;

<script type="text/javascript">
// <![CDATA[

function showTriggerDetails(item) {
    document.getElementById('trigdetails_'+item).style.display='block';
    document.getElementById('trigdetails_'+item).style.textAlign='left';
    document.getElementById('trigdetails_'+item).style.textFace='normal';
    document.getElementById('toggletext_'+item).innerHTML = '<a href="javascript:hideTriggerDetails('+item+');" style="text-decoration: none;"><b>($lt{'less'})</b></a>';
    return;
}

function hideTriggerDetails(item) {
    document.getElementById('trigdetails_'+item).style.display='none';
    document.getElementById('toggletext_'+item).innerHTML = '<a href="javascript:showTriggerDetails('+item+');" style="text-decoration: none;"><b>($lt{'more'})</b></a>';
    return;
}

// ]]>
</script>
ENDSCRIPT

}

1;

__END__

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>