#!/usr/bin/perl

## W3C CGIChacl - change access control list for a resource

#Copyright Massachusetts Institute of technology, 1998.
#Written by Eric Prud'hommeaux

#things that need to be done:
#1. purty it up (add icons)
#2. consider "like $resource%"

#####
# What It Does:
# presents form to edit ACLs for a resource. The form is preloaded with the 
# current ACLs for that resource. CGIChacl hands its output back to itself.

#####
# set up module environment

use strict;
use Exporter;

package W3C::Rnodes::CGIChacl;
use CGI;
use W3C::Util::W3CDebugCGI;
use W3C::Util::Exception qw(&throw &catch &DieHandler);
use W3C::Http::Exception;
use W3C::Util::Properties;
use W3C::XML::InputSource;
use W3C::Rdf::Algae2;

use W3C::Rnodes::ACL qw($ACCESS_CHACL $ACCESS_RACL
			$MACRO_RULENAME
			%TEXT_TYPE %TYPE_TEXT @DISPLAY_TYPES @DISPLAY_TYPES2
			%ACCESS_TEXT @ACCESS_LIST
			$ACL_SCHEMA_URI
			&parseDBRule &parseDBRule2 &parseDBRuleId &parseURI
			&accessBitFieldList &buildDBRuleId2
			&accessStringList &normalizeAccessStringList);

use W3C::Rnodes::ChaclConstants qw($DISPLAY_DIFFS $DISPLAY_LIST $DISPLAY_TABLE $DISPLAY_EXPERT $DISPLAY_RESOURCE_FIRST
				   %DISPLAY_STR_TO_CONST %DISPLAY_CONST_TO_STR @DISPLAY_TYPE_LIST);

use W3C::Rnodes::AclDB;
use W3C::Rdf::Atoms qw($RDF_SCHEMA_URI);

use vars qw($REVISION $VERSION $DSLI @ISA @EXPORT_OK);
@ISA = qw(Exporter);
$VERSION=0.99;
$REVISION = '$Id: CGIChacl.pm,v 1.59 2005/03/17 05:56:53 eric Exp $ ';
@EXPORT_OK = qw($SOURCE_chacl);

# We need to declare inheritance before the call to new so
# all the ISAs are up here.
@chacl::Renderer::Table::ISA = qw(chacl::Renderer);
@chacl::Renderer::WideTable::ISA = qw(chacl::Renderer::Table);
@chacl::Renderer::List::ISA = qw(chacl::Renderer);
@W3C::Rnodes::AclRdfErrorHandler::ISA = qw(W3C::Util::Exception);

# CONSTANTS
# paths
use vars qw($SCRIPT_HOME_URI_PROP);
$SCRIPT_HOME_URI_PROP = 'script.home.uri';

# Function to make submits available as strings or constants
use vars qw($SUBMIT_RenderPicks $SUBMIT_CheckSelected);
use vars qw($SUBMIT_S_More $SUBMIT_S_Detail $SUBMIT_M_DelBin $SUBMIT_M_Picks
	    $SUBMIT_M_RefreshList $SUBMIT_M_Review $SUBMIT_M_Revert
	    $SUBMIT_M_Copy $SUBMIT_C_Macro $SUBMIT_C_RDF $SUBMIT_C_Delete);
use vars qw(%SUBMIT_TYPES %SUBMIT_NAMES);
sub addSubmit {my ($value, $str) = @_; $SUBMIT_TYPES{$str} = $value; $SUBMIT_NAMES{$value} = $str;}

# Hundreds digit describes the category: (not used for logic at this point)
# S election	0x100
# M anipulation	0x200
# C ommit	0x400
# Tens digit controls some internal operation flags:
$SUBMIT_RenderPicks	= 0x010;
$SUBMIT_CheckSelected	= 0x020;
# Ones digit insures uniqueness:
$SUBMIT_S_More		= 0x111;	&addSubmit($SUBMIT_S_More, 'Add to ACLs:', '');
$SUBMIT_S_Detail	= 0x112;
$SUBMIT_M_DelBin	= 0x231;
$SUBMIT_M_Picks		= 0x202;	&addSubmit($SUBMIT_M_Picks, 'Make these changes to the RDF above', '');
$SUBMIT_M_RefreshList	= 0x213;	&addSubmit($SUBMIT_M_RefreshList, 'OK', '');
$SUBMIT_M_Review	= 0x204;	&addSubmit($SUBMIT_M_Review, 'Review this RDF (reparse and display)', '');
$SUBMIT_M_Revert	= 0x205;	&addSubmit($SUBMIT_M_Revert, 'Revert this RDF to the current ACLs', '');
$SUBMIT_M_Copy		= 0x226;	&addSubmit($SUBMIT_M_Copy, 'Copy ACLs From:', '');
$SUBMIT_C_Macro		= 0x421;
$SUBMIT_C_RDF		= 0x422;	&addSubmit($SUBMIT_C_RDF, 'Replace ACLs with RDF below:', '');
$SUBMIT_C_Delete	= 0x423;	&addSubmit($SUBMIT_C_Delete, 'Delete ACLs', '');
#SUBMIT_Concede		= 0x000;	&addSubmit($SUBMIT_Concede, 
#					           'I reluctantly concede in the interest of expediency.', '');

use vars qw($SESSION_HEADER $SESSION_LABEL $MAINTAINER);
$SESSION_HEADER = 'Session-Id';
$SESSION_LABEL = 'Session-Id';
$MAINTAINER = '<a href="mailto:eric@w3.org">Eric Prud\'hommeaux</a>';

use vars qw($QUERY_ACL_RULES $QUERY_ACL_BAG);

$QUERY_ACL_RULES = <<EOF
ask (?rule <${RDF_SCHEMA_URI}type> <${ACL_SCHEMA_URI}resourceAccessRule> .
     ?rule <${ACL_SCHEMA_URI}hasAccessTo> ?accessTo .
     ?rule <${ACL_SCHEMA_URI}accessor> ?accessor .
     ?rule <${ACL_SCHEMA_URI}access> ?access)
collect (?rule ?accessTo ?accessor ?access)
EOF
    ;

$QUERY_ACL_BAG = <<EOF
(ask '((${RDF_SCHEMA_URI}li ?bag %s))
 :collect '(?bag))
EOF
    ;

use vars qw($recursiveWildcard);
$recursiveWildcard = '...';

# SOURCE_chacl is the only ACL source common to all ACL systems. Generally,
# it is up to each importer to set a resource attribute to describe how that
# resource was inserted into the aclDB. The chacl script should give each
# importer its own constant and define its mappings to description strings and
# explanation links

use vars qw($SOURCE_chacl);
$SOURCE_chacl = 1;

use vars qw($PROP_NOTIFY_TO 
	    $PROP_NOTIFY_FROM $PROP_NOTIFY_SUBJ $MAIL_NOTIFY_BODY);
$PROP_NOTIFY_TO = 'notify.mail.to';
$PROP_NOTIFY_FROM = 'notify.mail.from';
$PROP_NOTIFY_SUBJ = 'notify.mail.subjectPrefix';
$MAIL_NOTIFY_BODY = 'blah blah blah';

sub main::main {
    my ($class, @args) = @_;
    my ($query, $chacl);
    eval {
	local($SIG{"__DIE__"}) = \&DieHandler;
	$W3C::Util::W3CDebugCGI::DEBUG_SESSION = $ARGV[1]; # use a session id like 957296047.909868;
	$query = new W3C::Util::W3CDebugCGI($0, $ARGV[0] eq 'DEBUG', 
					    {-storeIn => '/tmp', 
					     -dieNoOpen => 1, -logExt => '.log', 
					     -rerun => 'w3c_rerun', 
					     -reconstruct => 'reconstruct'});
	my $properties = new W3C::Util::Properties('chacl.prop');

	#####
	# main - either chaclCGI or show source
	if ($query->param('w3c_showSource')) {
	    &showMySources($query);
	} else {
	    $chacl = new $class($query, $properties, @args);
	    if ($query->param('w3c_resource') || $query->param('w3c_rdf_acl')) {
		$chacl->execute();
	    } else {
		$chacl->resourcePrompt($query);
	    }
	}
    }; if ($@) {
	my $sessionId = $query ? $query->getSessionId : undef;
	if (my $ex = &catch('W3C::Http::HttpMessageException')) {
	    if ($chacl && $chacl->{ACL_REPOSITORY}) {
		$chacl->{ACL_REPOSITORY}->disconnect;
		delete $chacl->{ACL_REPOSITORY};
	    }
	    my $message = $ex->getHttpMessage();
	    if (defined $sessionId) {
		$message->addHeader('Session-Id', $sessionId);
	    }
	    print $message->toString;
	} elsif ($ex = &catch('W3C::Rdf::CGIApp::AppException')) {
	    print $ex->toString($sessionId);
	} elsif ($ex = &catch('W3C::Util::Exception')) {
	    my $errorMessage = $ex->getMessage;
	    my $errorBody = $ex->toString;
    my $body = <<EOF
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>$errorMessage</title>
      <!-- Session-Id: $sessionId -->
   </head>
   <body bgcolor="#FFFFFF" text="#000000" link="#0000ee">

      <pre>$errorBody</pre>
<address>
  $SESSION_LABEL: $sessionId<br />
  <a href="http://validator.w3.org/check/referer"><img
     src="http://validator.w3.org/images/vh40" height=31 width=88
     align=right border=0 alt="Valid HTML 4.0!"></a>
  $MAINTAINER<br />
  $REVISION
</address>
   </body>
</html>
EOF
    ;
	    my $msg = new W3C::Http::HttpMessageException(-statusCode => 500, -headers => [], -body => $body);
	    print $msg->getHttpMessage->toString;
	} else {
	    print "Status: 500\n\n";
	    print "died with $@";
	    if ($sessionId) {
		print "<p>Session-id: $sessionId</p>\n";
	    }
	}
    }
}

#####
# CGIChacl instance variables:
#
# ACL_REPOSITORY	- all interractions with underlying ACL storage database
# ACL_IMPORTERS		- array of ACLQueryAgents
# ACL_DB		- W3C::Rnodes::AclDB - ACL rules database for displayed ACLs
# ACL_RDF		- SCALAR - string containing the RDF code for the displayed ACLs
# COPY_FROM		- CGI input for which URI to copy ACL rules from
# DELETE_BINS		- {DELETE_BINS}{bin}{type} = [ids] - id's to delete from a ACL bin
# DELETE_RESOURCES	- {DELETE_RESOURCES} = [URIs] - URIs to delete from ACL DB
# DEMAND_AUTH		- SCALAR - keep returning 401 until REMOTE_USER ne DEMAND_AUTH
# DETAIL_BINS		- {DETAIL_BINS}{bin}{type} = [ids] - id's to show details for
# DETAIL_RESOURCES	- {DETAIL_RESOURCES} = [URIs] - URIs to show details for
# DISPLAY		- SCALAR - DISPLAY_DIFFS|DISPLAY_LIST|DISPLAY_TABLE - how to render the ACLs
# MAX_ACCESS		- SCALAR - users maximum access to any of the selected resources
# MIN_ACCESS		- SCALAR - users minimum access to any of the selected resources
# NEXT_PICK_ID		- SCALAR - unique identifier for the next rendered picklist
# PICK_MORE_LIST	- {PICK_MORE_LIST} = [types] - extra types that the user may elected to fill in
# PICK_TYPES		- {PICK_TYPES}{pickId}{type} = [ids]
# READ			- CGI|W3C::Util::W3CDebugCGI = query object for CGI parameter interaction
# REQUIRE_SELECTED	- SCALAR - wheter to trim the RESROUCE_LIST to those resources in the SELECTED array
# RESOURCE_LIST		- [URIs] - resources to act on, either from user selection or from ACL_RDF
# SELECTED		- {SELECTED}{URI} = SCALAR - whether each URI has been selected to act on
# SOURCE		- {SOURCE}{URI} = SCALAR - source string for each ACL
# SUBMIT		- SCALAR - what type of submit button the user selected
# SUBMIT_IS_ACL_MACRO	- SCALAR - flag to indicate that the SUBMIT is a standard ACL macro
# WILDCARD		- SCALAR - what string to use as a wildcard for getAclsFor
# ----- coming soon -----
# ANNOTATE		- SCALAR - whether user has requested "helpful" annotation
# ORIGINAL_RESOURCE	- SCALAR - value of RESOURCE_LIST[0] first passed to CGIChacl

#####
# CGIChacl - present form to change ACLs
#

sub new {
    my $proto = shift;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);

    # constructor parameters
    ($self->{READ}, $self->{PROPERTIES}) = @_;
    $self->{WRITE} = new W3C::Util::W3CDebugCGI(undef, undef, {-noStore => 1});

    # references back to this same script
    # $self->{SELF_URL} = $ENV{'SCRIPT_NAME'};
    $self->{SELF_URI} = $self->{READ}->uriFromProperties($self->{PROPERTIES}, $SCRIPT_HOME_URI_PROP);
    if ($self->{SELF_URI} =~ m/([^\/]+\/?)$/) {
	$self->{SELF_PATH} = $1;
    } else {
	$self->{SELF_PATH} = $self->{URI};
    }

    return $self;
}

sub execute {
    my ($self) = @_;

    my $sessionId = $self->{READ}->getSessionId;
    $self->{-atomDictionary} = new W3C::Rdf::Atoms();

    # call loadAgents to load ACL_REPOSITORY and ACL_IMPORTERS
    eval {
	$self->loadAgents();
	if (my $prefsFile = $self->{PROPERTIES}->get('globalPrefsUri')) {
	    $self->readPrefs($prefsFile);
	}
    }; if ($@) {if (my $ex = &catch('W3C::Util::NoSuchFileException')) {
	&throw($self->confFileMissing($ex->getFile));
    } elsif ($ex = &catch('W3C::Util::Exception')) {
	&throw($self->unknownException($ex, ' reading Properties'));
    } else {
	&throw($self->unknownException($@, ' reading Properties'));
    }}

    # import CGI parameters
    $self->importCGIParms;

    # create AclDBs to store the current ACLs as well as those implied by the XML
    $self->{ACL_DB} = new W3C::Rnodes::AclDB;
    my ($newAclDB, $messageText);
    if ($self->{ACL_RDF}) {
	# call to chacl with state (eg ACLs from previous calls) passed in the RDF

	# assign a source document for RDF node identifiers of form EXTERNAL_ID#B1
	if (my $commaTool = $self->{PROPERTIES}->get('script.path.commaTool')) {
	    $self->{EXTERNAL_ID} = "$self->{ORIGINAL_RESOURCE}$commaTool$self->{WILDCARD}";
	} else {
	    $self->{EXTERNAL_ID} = $self->{SELF_URI};
	}

	# parse the RDF and handle exceptions
	eval {
	    $newAclDB = $self->parseRdfAcl();
	}; if ($@) {
	    my $exceptionMessage;
	    if (my $ex = &catch('W3C::Util::Exception')) {
		$exceptionMessage = $ex->toString;
	    } else {
		$exceptionMessage = $@;
	    }
	    $messageText = "Error Parsing RDF - reverting to original resources.\n";
	    $messageText .= $self->{WRITE}->escapeHTML($exceptionMessage);

	    # re-scan orig resources
	    $self->{RESOURCE_LIST} = $self->{READ}->param('w3c_originalResources');
	    $self->{SUBMIT} = undef;
	    $self->{SUBMIT_ACL_MACRO} = undef;
	    if (substr($self->{ACL_RDF}, 0, 1) eq '?') {
		$messageText .= "Your stupid client has trashed its data. Try quitting and restarting or running it on a different browser.<br />\n";
	    }
	} else {
	    # get RESOURCE_LIST from the list of resources listed in the RDF
	    $self->{RESOURCE_LIST} = [$newAclDB->getResources];
	}

	# handle some submit options here
	if ($self->{SUBMIT} == $SUBMIT_M_Revert) {
	    $newAclDB = undef;
	    $self->{REQUIRE_SELECTED} = 0;
	} elsif ($self->{SUBMIT} == $SUBMIT_M_Copy) {
	    if ($self->{COPY_FROM}) {
		# start with a fresh W3C::Rnodes::AclDB
		my $copyAclDB = new W3C::Rnodes::AclDB;
		$newAclDB = new W3C::Rnodes::AclDB;
		# fill rules database with access data for the copyFrom resource
		$self->importAcls($copyAclDB, [$self->{COPY_FROM}], undef, 0);

		my $newRules = $copyAclDB->rulesFor($self->{COPY_FROM});
		foreach my $resource (@{$self->{RESOURCE_LIST}}) {
		    $newAclDB->addResourceAttribute($resource, {'source', 'copied from '.$self->{COPY_FROM}});
		    foreach my $rule (@$newRules) {
			$newAclDB->addRule($resource, $rule->id, $rule->access);
		    }
		}
	    } else {
		# warn user of bad input
		$messageText.='<em>Copy ACLs from what?</em><br />'."\n";
		$messageText .= 'Please specify what resource you want to copy the ACLs from.<br />'."\n";
	    }
	}
    } else {
	if ($self->{ORIGINAL_RESOURCE}) {
	    # can't use ACL_RDF for RESOURCE_LIST so grab from ORIGINAL_RESOURCE
	    $self->{RESOURCE_LIST}[0] = $self->{ORIGINAL_RESOURCE};
	    $self->{REQUIRE_SELECTED} = 0;
	} else {
	    # first call to chacl so no state is stored in the RDF
	    $self->{ORIGINAL_RESOURCE} = $self->{RESOURCE_LIST}[0];
	}
    }

    # trim RESOURCE_LIST to those resources the user has selected
    if ($self->{REQUIRE_SELECTED} && !defined $self->{DEMAND_AUTH} && 
	($self->{SUBMIT} & $SUBMIT_CheckSelected)) {
	$self->{RESOURCE_LIST} = [grep {exists $self->{SELECTED}{$_}} @{$self->{RESOURCE_LIST}}];
    }

    # send a 401 if the client has requested an auth prompt. stop when identity changes.
    # Note: The 401 message contains the RESOURCE_LIST computed above. That's why it's here.
    if (defined $self->{DEMAND_AUTH} && $self->{DEMAND_AUTH} eq $ENV{'REMOTE_USER'}) {
	&throw($self->requireAuth('W3CACL', 'chacl & racl'));
    }

    my $wildcard = $self->{WILDCARD} || '*';

    # fill rules database with access data for this resource
    eval {
	$self->importAcls($self->{ACL_DB}, 
			  $self->{RESOURCE_LIST}, 
			  $self->{WILDCARD}, 
			  $wildcard eq $recursiveWildcard);
    }; if ($@) {
	my $exceptionMessage;
	if (my $ex = &catch('W3C::Http::HttpMessageException')) {
	    &throw($ex);
	} elsif (my $ex = &catch('W3C::Util::Exception')) {
	    $exceptionMessage = $ex->toString;
	} else {
	    $exceptionMessage = $@;
	}
	$messageText .= "\nError Importing ACLs for selected resources.\n";
	$messageText .= $self->{WRITE}->escapeHTML($exceptionMessage);
    }

    # update RESOURCE_LIST to represent actual resource list from the data sources
    $self->{RESOURCE_LIST} = [$self->{ACL_DB}->getResources];
    if (@{$self->{RESOURCE_LIST}} == 0) {
	&throw($self->emptyAclDatabase($messageText));
    }

    # find out what access the requesting client has
    $self->discoverClientsInclusions;
    if (($self->{MAX_ACCESS} & $ACCESS_RACL) != $ACCESS_RACL) {
	&throw($self->requireAuth('W3CACL', 'racl')); # @@@ only do this if the query makes mods. (editBits?)
    }

    # override rules database access with rdf data
    if ($newAclDB) {
	if (($self->{MAX_ACCESS} & $ACCESS_CHACL) != $ACCESS_CHACL) {
	    # we couldn't actually CHACL anything so demand a new identity
	    &throw($self->requireAuth('W3CACL', 'chacl'));
	} else {
	    # trim RESOURCE_LIST to those resources where we have CHACL privilege
	    $self->{RESOURCE_LIST} = [grep {
		$self->getEffectiveAccess($self->{ACL_DB}, $_) & $ACCESS_CHACL} @{$self->{RESOURCE_LIST}}];
	    # copy those resources to the current ACL_DB
	    $self->{ACL_DB} = $newAclDB->copy($self->{RESOURCE_LIST});

	    # Set the source for each of the copied resource for each resource.
	    # We just need to set this for Review as Pick takes care of itself.
	    if ($self->{SUBMIT} == $SUBMIT_M_Review) {
		foreach my $resource (@{$self->{RESOURCE_LIST}}) {
		    $self->{SOURCE}{$resource} = $SOURCE_chacl;
		}
	    }
	    eval {
		$self->discoverClientsInclusions;
	    }; if ($@) {if (my $ex = &catch('W3C::Rnodes::ACL::AclDataFormatException')) {
		# malformed/unknown ids in input RDF
		&throw($self->parseException($self->{ACL_RDF}, $ex->getMessage));
	    } else {
		# some other damned problem
		&throw($self->unknownException($@, ' checking new ACLs'));
	    }}
	}
    }

    # recompile new acl entry and set acl for resource
    my $aclEditText;
    eval {
	$aclEditText = $self->setNewAcls();
    }; if ($@) {if (my $ex = &catch('W3C::Util::Exception')) {
	&throw($ex);
    } else {
	&throw($self->unknownException($@, ' setting new ACLs'));
    }}

    # We only use the ACL_DB for rendering from now on so remove anything where
    # the user does not have RACL.
    $self->{ACL_DB}->deleteUnprivilegedResources($ACCESS_RACL);

    $self->{ACL_RDF} = $self->renderACLsAsRDF_XML();

    my $standardMacro = $self->getStandardMacro($self->{ACL_DB}, $self->{READ}->remote_user);
    { # find RESOURCE_BASE - eg (a/b/c,a/b/d,a/e/f) => a/
	my $list = $self->{RESOURCE_LIST};
	my $base;
	my @segments = split (/(\/)/, $list->[0]);
	my $lastSegment = $segments[-1] eq '/' ? @segments : @segments-1;
      SEGMENT:
	for (my $segment = $lastSegment; $segment > 0; $segment-=2) {
	    $base = join ('', @segments[0..$segment-1]);
	    foreach my $resource (@$list) {
		next SEGMENT if ($resource !~ m/^\Q$base\E/x);
	    }
	    last SEGMENT;
	}
	$self->{RESOURCE_BASE} = $base;
    }


    # build accept table of the form ('text/html'=>0.7, 'text/html;level=1'=>1, '*/*'=>0.5, 'text/*'=>0.3)
    my %accepts = map {($_ =~ m/(.*?)\;q=(.*)/) ? ($1, $2) : ($_, 1)} split(/[,\s]+/, $ENV{'HTTP_ACCEPT'});

    # Content negotiation determines the return document form and mime-type.
    # We support text/x-rdf, text/plain (sort of), and text/html.
    my ($document, $contentType);
    if ((%accepts && 
	 ($accepts{'text/xml'} >= $accepts{'*/*'} && 
	  $accepts{'text/xml'} >= 2*$accepts{'text/html'})) || 
	$self->{FORCE_RDF}) {

	# render in XML/RDF
	$document = "<?xml version=\"1.0\"?>\n<!-- Session-Id: $sessionId -->\n$self->{ACL_RDF}\n";
	$contentType = 'text/xml';
    } elsif (%accepts && 
	     ($accepts{'text/plain'} >= $accepts{'*/*'} && 
	      $accepts{'text/plain'} > $accepts{'text/html'})) {

	# also render in XML/RDF
	$document = "<?xml version=\"1.0\"?>\n<!-- Session-Id: $sessionId -->\n$self->{ACL_RDF}\n";
	$contentType = 'text/plain';
    } else {
	# render in html (this one's a little longer)
	$contentType = 'text/html; charset=iso-8859-1';
	
	my $title = $self->{SUBMIT} == 0 ? 'Edit ACLs for ' : 'Updated ACLs for ';
	my $resourceList = '"'.join('", "', @{$self->{RESOURCE_LIST}}).'"';
	my $resourceString = length $resourceList < 100 ? $resourceList : $self->{RESOURCE_BASE}.$self->{WILDCARD};
	$title .= $resourceString;
	my $writeUrl = $self->getSelfUri;
	use W3C::Rnodes::ChaclHelp;
	$self->{HELP_TEXT} = $self->{ANNOTATE} ? new W3C::Rnodes::ChaclHelp() : new W3C::Rnodes::NoHelp();

	# These local variables are used to keep store all the pieces of XHTML
	# so we can render the whole thing at once.
	my ($demandAuth, $remoteUser, $standardIconText, 
	    $renderedAcls, $toggleButtons) = ('', '', '', '', '');

	my $helpSummary = $self->{ANNOTATE} ? 
	    "Layout table: The first cell contains an image which is a link to the W3C homepage, the second cell contains a help button, the third cell contains a " : 
		"Layout table: The first cell contains an image which is a link to the W3C homepage, the second cell contains a help button, the third cell is mysteriously empty and the fourth cell shows your current authentication ID (if you have authenticated) and a prompt to change your ID";
	my $helpRowLayout = $self->{ANNOTATE} ? "</td>
  </tr>
  <tr>
    <td>\n" : "</td><td>\n";

	my $helpButton;
	{
	    my $name = $self->{ANNOTATE} ?  'w3c_helpHide' : 'w3c_helpShow';
	    my $value = $self->{ANNOTATE} ?  'Hide Help' : 'Show Help';
	    $helpButton = $ENV{REQUEST_METHOD} eq 'POST' ? 
		"      <input type=\"submit\" name=\"$name\" value=\"$value\" />\n" :
		    "      ".$self->linkBack($value, 
					     $self->{ORIGINAL_RESOURCE}, 
					     [@{$self->wildcardParm(undef)}, "$name=1"])."\n";
	}

	if (int ($self->{MIN_ACCESS} & ($ACCESS_RACL|$ACCESS_CHACL)) != int ($ACCESS_RACL|$ACCESS_CHACL)) {
	    my $buttonOrLink = $ENV{REQUEST_METHOD} eq 'POST' ? 
		"      <input type=\"submit\" name=\"w3c_demand_auth_$ENV{'REMOTE_USER'}\" value=\"Change Authentication\" />\n" :
		    "      ".$self->linkBack('change identity', 
					     $self->{ORIGINAL_RESOURCE}, 
					     [@{$self->wildcardParm(undef)}, "w3c_demand_auth_$ENV{'REMOTE_USER'}=1"])."\n";
	    $remoteUser = $self->{HELP_TEXT}{RenderAuthButton}.$buttonOrLink;
	}
	if ($ENV{'REMOTE_USER'}) {
	    $remoteUser = $self->{HELP_TEXT}{CurrentId}.
		"      current ID: <b>$ENV{REMOTE_USER}</b>";
	}

	my $renderedRenderOptions = $self->renderRenderOptions;

	if ($messageText) {
	    $messageText = "<h1>Errors</h1>\n<pre>$messageText</pre>\n";
	}

	if ($aclEditText) {
	    $aclEditText = "<h1>Updates</h1>\n<pre>$aclEditText</pre>\n";
	}

	my $standardDesc = $self->standardMacroToDesc($standardMacro);
	my $standardIcon = $self->standardMacroToIcon($standardMacro);
#	$standardIcon = '/Icons/dot.gif' 
	if ($standardIcon) {
	    $standardIcon = 'http://www.w3.org'.$standardIcon;
	    $standardIconText = "<div><img src=\"$standardIcon\" alt=\"$standardDesc access\" style=\"color: white; border: none\" /></div>\n";
	}

	# find the shortest uri in the resource list
	my $shortestUri = $self->{RESOURCE_LIST}[0];
	foreach my $resource (@{$self->{RESOURCE_LIST}}) {
	    if ($resource !~ m/^$shortestUri/) {
		# copy longest substring into $shortestUri
		my $lr = length($resource);
		my $ls = length($shortestUri);
		my $l = ($lr < $ls ? $lr : $ls);
		my $i = 0;
		for ($i = 0; $i < $l; $i++) {
		    if (substr($resource, $i, 1) ne substr($shortestUri, $i, 1)) {
			last;
		    }
		}
		$shortestUri = substr($shortestUri, 0, $i);
	    }
	}
	# trim to next-higher directory
	$shortestUri =~ s/[^\/]*\Z//;
	{
	    # make a copy of the ORIGINAL_RESOURCE
	    my $orig = $self->{ORIGINAL_RESOURCE};
	    # trim trailing '/*' (where '*' is really the WILDCARD)
	    $orig =~ s/\/?\Q$self->{WILDCARD}\E\Z/\//;
	    if ($shortestUri ne $orig) {
		$self->{DIRECTORY_SCAN} = $shortestUri;
	    }
	}

	# render current (after any changes) ACLs in a GET form
	$self->{REQUIRE_SELECTED} = 0;
	$renderedAcls = $self->renderACLs($standardMacro);


	if ($self->{BUTTON_NAMES} && @{$self->{BUTTON_NAMES}}) {
	    # script to toggle checkboxes
	    # -1 turn off
	    #  0 toggle
	    #  1 turn on
	    $toggleButtons = '
<!-- code for selection toggle buttons -->
<div class="toggleButtons">
  <script type="text/javascript"><!-- 
function allCheckBoxes(action) {
    var fields = new Array;
';
	    for (my $i = 0; $i < @{$self->{BUTTON_NAMES}}; $i++) {
		my $buttonName = $self->{BUTTON_NAMES}[$i];
		$toggleButtons .= '    fields['.$i.'] = "'.$buttonName."\";\n";
	    }
	    $toggleButtons .= '
    for (var i = 0; i < fields.length; i++) {
	var field = eval("document.forms[0]." + fields[i]);
	if (action == -1 || (action == 0 && field.checked == true)) {
	    field.checked = false;
	} else {
	    field.checked = true;
	}
    }
} //-->
  </script>

  <!-- buttons to use javascript to togge the URI selections -->
  <input type="button" value="all off" onclick="allCheckBoxes(-1)" />
  <input type="button" value="all on" onclick="allCheckBoxes(1)" />
  <input type="button" value="toggle selection" onclick="allCheckBoxes(0)" />
</div>

';
	}

	# ----------------------------- HTML PAGE -----------------------------
	# Now that we've done most of the work, we layout the page in 8 blocks:

	# spare doctype for trailing edge browsers
#<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
	$document =<<EOHEADER
<?xml version=\"1.0\"?>
<!-- Session-Id: $sessionId -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>$title</title>
    <link href="http://www.w3.org/StyleSheets/Core/chaclCGI.css" rel="stylesheet" type="text/css" />
    $self->{-htmlHeaderAddendum}
  </head>
  <body>
    <form method="post" action="$writeUrl" enctype="application/x-www-form-urlencoded" >

<!-- help options -->
<table summary="$helpSummary">
  <tr>
    <td>
      <a href="http://www.w3.org/"><img style="color: white; border:none" 
	  src="http://www.w3.org/Icons/WWW/w3c_home" alt="W3C Home" /></a>
    $helpRowLayout
    $self->{HELP_TEXT}{Intro}
    $self->{HELP_TEXT}{Help}
    $helpButton
      <input type=\"hidden\" name=\"w3c_defaultDisplay\" value=\"$self->{DISPLAY}\" />
      <input type=\"hidden\" name=\"w3c_helpDefault\" value=\"$self->{ANNOTATE}\" />
    </td><td>
    $helpRowLayout
      $demandAuth
      $remoteUser
    </td>
  </tr>
</table>

$self->{HELP_TEXT}{RenderOptions}
$renderedRenderOptions
$messageText
$aclEditText

<h1>ACL Details</h1>
$standardIconText

<!-- table of URIs and their ACLs -->
<p>for: <tt>$shortestUri</tt></p>
$self->{HELP_TEXT}{RenderACLs}
$renderedAcls

$toggleButtons
EOHEADER
    ;

	if ($self->{ORIGINAL_RESOURCE}) {
	    my ($rescanOrigResources, $scanCurrentDirectory, $scanParentDirectory);
	    # rescan original link
	    if (@{$self->{RESOURCE_LIST}} == 1 && 
		$self->{RESOURCE_LIST}[0] eq $self->{ORIGINAL_RESOURCE}) {
	    } else {
		my $linkBackOrig = $self->linkBack('rescan: '.$self->{ORIGINAL_RESOURCE}, 
						   $self->{ORIGINAL_RESOURCE}, $self->wildcardParm(undef));
		$rescanOrigResources = "$self->{HELP_TEXT}{RescanOrigResources}$linkBackOrig";
	    }

	    # scan directory link
	    if ($self->{DIRECTORY_SCAN}) {
		$scanCurrentDirectory = $self->linkBack('scan directory: '.$self->{DIRECTORY_SCAN}, 
							$self->{DIRECTORY_SCAN}.$wildcard, $self->wildcardParm('*'));
	    }

	    # parent directory link
	    if ((&parseURI($shortestUri))[3] ne '/') {
		my $tmp = $shortestUri;
		$tmp =~ s/[^\/]+\/\Z//;
		my $parent = $tmp.$wildcard;
		if ($parent ne $self->{ORIGINAL_RESOURCE}) {
		    $scanParentDirectory = $self->linkBack('scan parent directory: '.$tmp, 
							   $tmp.$wildcard, $self->wildcardParm('*'));
		}
	    }


	    $document .=<<EOF
<!-- links to rescan ACLs for resources -->
<p class="rescan_links">
$rescanOrigResources
$scanCurrentDirectory
$scanParentDirectory
</p>

<!-- store initial parameters for rescan links -->
<div class="origState">
  <input type="hidden" name="w3c_originalResources" value="$self->{ORIGINAL_RESOURCE}" />
  <input type="hidden" name="w3c_wildcard" value="$self->{WILDCARD}" />
</div>

EOF
    ;
	}

	my $renderedRequireSelect = $self->{WRITE}->cacheHidden('w3c_require_select', $self->{REQUIRE_SELECTED});
	my $renderedSources = '';

	# write out the sources for the ACLs in the state RDF
	foreach my $resource (keys %{$self->{SOURCE}}) {
	    my $source = $self->{SOURCE}{$resource};
	    my $name = 'w3c_source_'.W3C::Util::W3CDebugCGI::escapeName($resource);
	    $renderedSources .= '  '.$self->{WRITE}->cacheHidden($name, $source)."\n";
	}

	$document .=<<EOF
<!-- store where each ACL came from -->
<div class="sources">
  $renderedRequireSelect
$renderedSources
</div>

EOF
    ;

	if (@{$self->{RESOURCE_LIST}} > 0) {
	    # render editing options in a POST form
	    my $readOnly = (int ($self->{MAX_ACCESS} & ($ACCESS_CHACL)) != int ($ACCESS_CHACL));
	    if (!$readOnly) {
		my $heading = 'Set access level for the '.($self->{REQUIRE_SELECTED} ? 'selected' : 'above').
		    ' resource'.(@{$self->{RESOURCE_LIST}} > 1 ? 's' : '').':';
		$document .=<<EOF
<h2>$heading</h2>
$self->{HELP_TEXT}{RenderMacros}
EOF
    ;
	    }
	    if ($self->{DISPLAY} & $DISPLAY_EXPERT) {
		if ($readOnly) {
		    my $renderedAclRdf = '<pre>'.$self->{WRITE}->escapeHTML($self->{ACL_RDF}).'</pre>';
		    $document .=<<EOF
<hr />

<p class=\"expertInput\">

$self->{HELP_TEXT}{RenderAclRdf}
$renderedAclRdf
$self->{HELP_TEXT}{RenderPicks}
</p>

EOF
    ;
		} else {
		    my $renderedMacros = $self->renderMacros(1);
		    my $renderedAclRdf = $self->renderAclRdf;
		    my $renderedPicks = ($self->{SUBMIT} & $SUBMIT_RenderPicks) ? $self->renderPicks : '';
		    my $renderedDetails = $self->renderDetails;
		    my $link = $self->{WRITE}->escapeHTML($self->getThisLink);
		    $document .=<<EOF
$renderedMacros
<hr />

<!-- input fields that show up in expert mode 
     These manipulate only the RDF and could go into a GET form. -->
<p><span class=\"fixme\"><strong>ACL Details shown ARE NOT
the ones in effect until you click on the \"Replace ACLs with RDF below\" 
button.</strong></span></p>

<p class=\"expertInput\">

$self->{HELP_TEXT}{RenderAclRdf}
$renderedAclRdf
$self->{HELP_TEXT}{RenderPicks}
</p>

$renderedPicks
$self->{HELP_TEXT}{RenderDetails}
$renderedDetails
EOF
    ;
		}
	    } else {
		if ($readOnly) {
		} else {
		    my $tmp = $self->{WRITE}->escapeHTML($self->{ACL_RDF});
		    my $renderedMacros = $self->renderMacros(0);
		    $document .=<<EOF
$renderedMacros
<!-- store the state in RDF -->
<p class="hiddenStateRDF">
<input type="hidden" name="w3c_rdf_acl" value="$tmp" />
</p>

EOF
    ;
		}
	    }
	    $document .= $self->getSourceDescriptions();
	}

	# done - clean up
	$document .=<<EOBOTTOM
<!-- close the very big form -->
    </form>
    <hr />

    <!-- a gesture to accountability -->
    <address>
      $SESSION_LABEL: $sessionId<br />
      <a href="http://validator.w3.org/check/referer"><img
         src="http://www.w3.org/Icons/valid-xhtml10" height="31" width="88"
         style="color: white; border:none; float:right" alt="Valid XHTML 1.0!" /></a>
      $MAINTAINER<br />
      $REVISION
    </address>
  </body>
</html>
EOBOTTOM
    ;
	# --------------------------- end HTML PAGE ---------------------------

    }

    # print our document
    print $self->standardHeader(200, $sessionId, $contentType, length $document);
    print $document;

    # tell db's to clean themselves up
    $self->{ACL_REPOSITORY}->disconnect;
    delete $self->{ACL_REPOSITORY}

    # that's it, we're done!
}

sub loadAgents {
    my ($self) = @_;
    # pure virtual 
    &throw(new W3C::Util::NotImplementedException());
}

sub getEffectiveAccess {
    my ($self, $aclDB, $resource) = @_;
    return $aclDB->getResourceAttribute($resource, 'access');
}

sub getSourceDescriptions {
    my ($self) = @_;
    # pure virtual 
    &throw(new W3C::Util::NotImplementedException());
}

sub helpCall {
    my ($self, $funcCall, @args) = @_;
    return $self->{HELP_TEXT}->$funcCall(@args);
}

sub getSelfUriUNDERLOAD {
    my ($self)  = @_;
    return $self->{SELF_URI};
}

sub getSelfUri {
    my ($self) = @_;
    my $ret;
    if (my $commaTool = $self->{PROPERTIES}->get('script.path.commaTool')) {
	$ret = "$self->{ORIGINAL_RESOURCE}$commaTool$self->{WILDCARD}";
    } else {
	$ret = $ENV{'SCRIPT_NAME'};
    }
    return $ret;
}

sub getSelfPath {
    my ($self)  = @_;
    return $self->{SELF_PATH};
}

sub standardHeader {
    my ($self, $retCode, $sessionId, $mimeType, $length) = @_;
    my @headerLines = ();
    push (@headerLines, "Status: 200");
    push (@headerLines, "Content-Type: $mimeType");
    if (defined $length) {
	push (@headerLines, "Content-length: $length");
    }
    push (@headerLines, "$SESSION_HEADER: $sessionId");
    push (@headerLines, ("", ""));
    join ("\n", @headerLines);
}

# make a GETtable relative link back to this form
sub linkBack {
    my ($self, $label, $resource, $parms) = @_;
    my $link;
    if (my $commaTool = $self->{PROPERTIES}->get('script.path.commaTool')) {
	my $wildcard = (map {$_ =~ m/^w3c_wildcard=(.*)$/} @$parms)[0];
	my @copy = grep {$_ !~ m/^w3c_wildcard=/} @$parms;
	$link = join ('&', (@copy));
	$link = join ('?', ("$resource$commaTool$wildcard", $link));
    } else {
	$link = $ENV{'SCRIPT_NAME'}.'?'.$self->urlParms('w3c_resource='.&CGI::escape($resource), @$parms);
    }
    # my $link = $self->getSelfPath().'?'.$self->urlParms('w3c_resource='.&CGI::escape($resource), @$parms);
    return "<a href=\"$link\">$label</a><br />";
}

sub readPrefs {
    my ($self, $prefs) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub getThisLink () {
    my ($self) = @_;
    my @ret;
    push (@ret, 'w3c_display='.$self->{WRITE}->escape($self->{DISPLAY})) if ($self->{ACL_RDF});
    push (@ret, 'w3c_submit='.$self->{WRITE}->escape($SUBMIT_NAMES{$self->{SUBMIT}})) if ($self->{ACL_RDF});
    push (@ret, $self->{WRITE}->showCache);
    push (@ret, 'w3c_rdf_acl='.$self->{WRITE}->escape($self->{ACL_RDF})) if ($self->{ACL_RDF});
    return $self->{READ}->url.'?'.join ('&', @ret);
}

sub endMessageNUKE {
    my ($self, $message) = @_;
    print $message;
    $self->{ACL_REPOSITORY}->disconnect;
}

sub arrayHasDiffs {
    my ($a, $b) = @_;
    return 1 if (@$a != @$b);
    my (%a, %b);
    map {$a{$_} = undef} @$a;
    map {return 1 if (!exists $a{$_})} @$b;
    return 0;
}

#####
# canonicalizeUri

sub canonicalizeUri {
    my ($self, $resource) = @_;
    # return of canonical already
    return lc($1).'://'.lc($2).($3 eq ':80' ? '' : $3).$4 if ($resource =~ m/\A(\w+):\/\/([a-zA-Z0-9_\.\-\*]+)(:\d+)?(\/.*)\Z/);

    # resource assumed to be just path so build URI from host and resource
    my $host = $self->{PROPERTIES}->getI('canonicalHost');
    my ($server, $port) = split(':', $host);
    my $default = 'http://'.lc($server);
    $default .= ':'.$port if ($port && $port != 80);
    return $default.$resource if ($resource =~ m/\A(\/.*)\Z/);
    return $default.'/'.$resource;
}

# CGI inputs:
#  w3c_resource: change ACLs for resource
#  CGI submit buttons:
#   w3c_submit_ ...
# 		nobody
# 		everybody
# 		team
# 		member
# 		add
#		replacePick
#		replaceAccess
# 		detail
#  CGI form detail options:
#   w3c_detail_ ...
# 		access[_<access>]
# 		group[_<group>]
# 		user[_<user>]
# 		org[_<org>]
# 		ip[_<ip>]
#  CGI form pick list results:
#   w3c_pick_ ...
# 		group
# 		user
# 		org
# 		ip

sub importCGIParms {
    my ($self) = @_;

    # locally stored defaults
    my $defaultHelp = undef;
    my $defaultDisplay = undef;

    # read the inputs from the query
    my @params = $self->{READ}->param;
    foreach my $param (@params) {
	# entry into the chacl script through a specified resource and optional wildcards
	if ($param eq 'w3c_resource') {
	    $self->{RESOURCE_LIST} = [$self->canonicalizeUri($self->{READ}->param('w3c_resource'))];
	} elsif ($param eq 'w3c_wildcard') {
	    my $wildcard = $self->{READ}->param($param);
	    if ($wildcard ne '') {
		$self->{WILDCARD} = $wildcard;
	    }
	} elsif ($param eq 'w3c_originalResources') {
	    ($self->{ORIGINAL_RESOURCE} = $self->{READ}->param($param));

	# subsequent interractions communicate state by manipulating the RDF
	} elsif ($param eq 'w3c_rdf_acl') {
	    $self->{ACL_RDF} = $self->{READ}->param('w3c_rdf_acl');

	# display preferences
#	($param eq 'w3c_display') && 
#	    (map {$self->{DISPLAY} |= $DISPLAY_STR_TO_CONST{$_};} $self->{READ}->param('w3c_display'));
	} elsif ($param eq 'w3c_display') {
	    map {$self->{DISPLAY} |= int($_);} $self->{READ}->param('w3c_display');
	} elsif ($param eq 'w3c_defaultDisplay') {
	    $defaultDisplay = $self->{READ}->param('w3c_defaultDisplay');
	} elsif ($param eq 'w3c_helpShow') {
	    $self->{ANNOTATE} = 1;
	} elsif ($param eq 'w3c_helpHide') {
	    $self->{ANNOTATE} = 0;
	} elsif ($param eq 'w3c_helpDefault') {
	    $defaultHelp = $self->{READ}->param('w3c_helpDefault');

	# user may request an auth challenge to change identity
	} elsif ($param =~ m/\Aw3c_demand_auth_(.*)\Z/ && $self->{READ}->param($param) ne '') {
	    $self->{DEMAND_AUTH} = $1;

	# details and deletes from renderResourceList
	# <input type="checkbox" name="w3c_select_http://ella.w3.org/1998/07/14-sampleSpec.html" checked> 
	# <a href="http://ella.w3.org/blah"><font color="green">http://ella.w3.org/blah 
	# <INPUT TYPE="submit" NAME="w3c_delete_resource_http://ella.w3.org/1998/07/14-sampleSpec.html" VALUE="X">
	} elsif ($param =~ m/\Aw3c_select_(.*)\Z/ && $self->{READ}->param($param) eq 'on') {
	    $self->{SELECTED}{$self->{READ}->unescapeName($1)} = 1;
	} elsif ($param =~ m/\Aw3c_source_(.*)\Z/) {
	    $self->{SOURCE}{$self->{READ}->unescapeName($1)} = $self->{READ}->param($param);
	} elsif ($param eq 'w3c_require_select') {
	    $self->{REQUIRE_SELECTED} = $self->{READ}->param($param);
	} elsif ($param =~ m/\Aw3c_detail_resource_(.*)\Z/) {
	    push (@{$self->{DETAIL_RESOURCES}}, $1);
	    $self->{SUBMIT} = $SUBMIT_S_Detail;

	# details and deletes from renderAccessList
	# <INPUT TYPE="submit" NAME="w3c_detail_bin_B0_user" VALUE="eric"> has 
	# <INPUT TYPE="submit" NAME="w3c_detail_bin_B0_access" VALUE="racl,head,get"> access. - ???
	# <INPUT TYPE="submit" NAME="w3c_delete_bin_B0_user_eric" VALUE="X"></font></li>
	} elsif ($param =~ m/\Aw3c_detail_bin_([^_]+)_([^_]+)_(.*)\Z/) {
	    push (@{$self->{DETAIL_BINS}{$1}{$2}}, $3);
	    $self->{SUBMIT} = $SUBMIT_S_Detail;
	} elsif ($param =~ m/\Aw3c_delete_bin_([^_]+)_([^_]+)_(.*)\Z/) {
	    push (@{$self->{DELETE_BINS}{$1}{$2}}, $3);
	    $self->{SUBMIT} = $SUBMIT_M_DelBin;
	
	# picks from buildPickListSet
	# the identifyer in the middle ( type and id of form=: ([^_]+) )is because we may have more than one pick list
	} elsif ($param =~ m/\Aw3c_pick_type_([^_]+)_(.+)\Z/) {
	    push (@{$self->{PICK_TYPES}{$1}{$2}}, $self->{READ}->param($param));
	} elsif ($param =~ m/\Aw3c_pick_access_([^_]+)\Z/) {
	    push (@{$self->{PICK_ACCESS}{$1}}, $self->{READ}->param($param));
	} elsif ($param =~ m/\Aw3c_pick_resources_([^_]+)\Z/) {
	    push (@{$self->{PICK_RESOURCES}{$1}}, $self->{READ}->param($param));
	} elsif ($param =~ m/\Aw3c_pick_moreList_([^_]+)\Z/) {
	    push (@{$self->{PICK_MORE_LIST}{$1}}, $self->{READ}->param($param));
	} elsif ($param eq 'w3c_pick_moreList') {
	    push (@{$self->{PICK_MORE_LIST}}, $self->{READ}->param($param));

	# actions that have immediate DB side-effects
	} elsif ($param eq 'w3c_submit') {
	    $self->{SUBMIT} = $self->submitValueFor($self->{READ}->param($param));
	} elsif ($param eq 'w3c_macro') {
	    $self->{SUBMIT_ACL_MACRO} = $self->standardDescToMacro($self->{READ}->param($param));
	    $self->{SUBMIT} = $SUBMIT_C_Macro;

	# other actions that manipulate the RDF
	} elsif ($param eq 'w3c_copyFrom') {
	    $self->{COPY_FROM} = $self->{READ}->param($param);

	# output control parameters
	} elsif ($param eq 'w3c_forceRdf') {
	    $self->{FORCE_RDF} = $self->{READ}->param($param);
	} else {
	    &throw(new W3C::Util::Exception(-message => "unknown parameter: $param"));
	}
    }

    # assign defaults
    if (!$self->{DISPLAY}) {
	$self->{DISPLAY} = defined $defaultDisplay ? $defaultDisplay : $DISPLAY_TABLE|$DISPLAY_DIFFS;
    }

    if (!defined $self->{ANNOTATE}) {
	$self->{ANNOTATE} = defined $defaultHelp ? $defaultHelp : 0;
    }

    return 1;
}

#####
# parseRdfAcl - read ACLs from XML/RDF
# returns - constructed W3C::Rnodes::AclDB

sub parseRdfAcl {
    my ($self) = @_;
 
    use W3C::Rdf::RdfApp;
    my $rdfApp = new W3C::Rdf::RdfApp(-atomDictionary => $self->{-atomDictionary});
    # $rdfApp->{ARGS}{-files} = [$prefs];
    my $inputSource = &W3C::Rdf::RdfApp::getInputSource(\ $self->{ACL_RDF});
    $inputSource->setPublicId($self->{EXTERNAL_ID});
    my $queryHandler = $rdfApp->getQueryHandler($self);
    $rdfApp->parse($inputSource->getByteStream(), 
		   $inputSource->getPublicId(), 
		   'RDFXML', 
		   $queryHandler);
    my $rdfDB = $rdfApp->{RDF_DB};
    my $aclDB = new W3C::Rnodes::AclDB;
    # $sysId = $rdfDB->getUri($self->getSelfPath() ? 'http://'.$ENV{'SERVER_NAME'}.$self->getSelfPath() : 'http://www.w3.org/');
    my $idAtom = $self->{-atomDictionary}->getUriLink($self->{EXTERNAL_ID}, undef);
    my $nsh = new W3C::Util::NamespaceHandler();
    my $qh = new W3C::Rdf::Algae2($self->{-atomDictionary}, $nsh, {'' => $self}, $rdfApp, 
				  undef, {-uniqueResults => 1}, -defaultDB => $rdfDB);
    $qh->setSourceAttribution($rdfApp->makeInputAttribution($self->{EXTERNAL_ID}));
    my ($rows, $selects, $messages, $statements) = $qh->interpret($QUERY_ACL_RULES, undef, $QL_ALGAE, 0x00);

    # +-----------------+---------+--------------------+--------------+
    # |             rule| accessTo|            accessor|        access|
    # +-----------------+---------+--------------------+--------------+
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|<acls:connect>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|    <acls:get>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|<acls:options>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>| <acls:delete>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|   <acls:racl>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|  <acls:chacl>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|   <acls:head>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|  <acls:trace>|
    # | _:<doc:/chacl>:1|    <doc>|           <user:ot>|    <acls:put>|
    # |_:<doc:/chacl>:25|    <doc>|         <unixID::0>|  <acls:trace>|
    # |_:<doc:/chacl>:25|    <doc>|         <unixID::0>|    <acls:get>|
    # |_:<doc:/chacl>:25|    <doc>|         <unixID::0>|   <acls:head>|
    # |_:<doc:/chacl>:25|    <doc>|         <unixID::0>|   <acls:racl>|
    # |_:<doc:/chacl>:25|    <doc>|         <unixID::0>|<acls:options>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|    <acls:get>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|   <acls:racl>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|<acls:connect>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|  <acls:trace>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|<acls:options>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|  <acls:chacl>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|   <acls:head>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>| <acls:delete>|
    # |_:<doc:/chacl>:13|    <doc>|<group:w3cteamgroup>|    <acls:put>|
    # +-----------------+---------+--------------------+--------------+
    # Gather repeated acls:access properties into an array

    my $byRule = {};
    my ($curMacro, $curRule);
    foreach my $row (@$rows) {
	my ($rule, $accessTo, $accessor, $access) = @$row;
	($accessTo, $accessor, $access) = 
	    ($accessTo->getUri, $accessor->getUri, $access->getUri);
	if (exists $byRule->{$rule}{$accessTo}{$accessor}) {
	    push (@{$byRule->{$rule}{$accessTo}{$accessor}}, $access);
	} else  {
	    $byRule->{$rule}{$accessTo}{$accessor} = [$access];
	}
    }
    foreach my $rule (keys %$byRule) {
	my $byAccessTo = $byRule->{$rule};
	foreach my $accessTo (keys %$byAccessTo) {
	    my $byAccessor = $byAccessTo->{$accessTo};
	    foreach my $accessor (keys %$byAccessor) {
		my $access = $byAccessor->{$accessor};
		$aclDB->addRule($accessTo, $accessor, $access);
		$aclDB->addResourceAttribute($accessTo, 
					     {'source' => $SOURCE_chacl});
	    }
	}
    }

    return $aclDB;
}

sub renderACLsAsRDF_XML {
    my ($self) = @_;
    my $xmlBase = $ACL_SCHEMA_URI;
    $xmlBase =~ s/\#$//; # get around bug in xml base code
    my $str = join("\n", map {"   $_"} 
		   $self->{ACL_DB}->toRdfXml('r:', 's:', $xmlBase));
    my $ret = <<EOF;
<r:RDF xml:base=\"$xmlBase\"
 xmlns:r=\"$RDF_SCHEMA_URI\"
 xmlns:s=\"$ACL_SCHEMA_URI\">
$str
</r:RDF>
EOF
    ;
    my %resourceBins = $self->{ACL_DB}->getResourceBins;
    my ($nextBag, $un) = (0, 0);
    foreach my $bin (keys %resourceBins) {
	$nextBag++;
	my $bagId = 'B'.$nextBag;
	foreach my $resource (@{$resourceBins{$bin}[0]}) {
	    # my $source = $self->{ACL_DB}->getResourceAttribute($resource, 'source');
	    $self->{ACL_DB}->addResourceAttribute($resource, 'aclBinBag', $bagId);
	}
    }
    return $ret;
}

# build array of wildcards

sub wildcardParm {
    my ($self, $defaultWildcard) = @_;
    my @components = ();
    if (my $tmp = ($self->{WILDCARD} || $defaultWildcard)) {
	push (@components, 'w3c_wildcard='.$tmp);
    }
    return [@components];
}

# make a uri from the components

sub urlParms {
    my ($self, @components) = @_;
    return join ('&amp;', @components);
}

#####
# submitValueFor - look up integer value for a submit string

sub submitValueFor {
    my ($self, $str) = @_;
    my $ret = $SUBMIT_TYPES{$str};
    if (!defined $ret) {
	foreach my $key (keys %SUBMIT_TYPES) {
	    if ($str =~ m/\A$key/) {
		return $SUBMIT_TYPES{$key};
	    }
	}
	&throw(new W3C::Util::Exception("bogus submit string: \"$str\""));
    }
    return $ret;
}

sub importAcls {
    my ($self, $aclDB, $resourceList, $wildcard, $recursive) = @_;
    my $added = 0;
    foreach my $importer (@{$self->{ACL_IMPORTERS}}) {
	eval {
	    $added += $self->invokeImporter($importer, $aclDB, $resourceList, $wildcard, $recursive);
	}; if ($@) {if (my $ex = &catch('W3C::Rnodes::UnixPermAgent::NeedRedirectException')) {
	    &throw($self->redirect($ex->getTarget, $ex->getUri));
	} elsif ($ex = &catch('W3C::Util::NoSuchFileException')) {
	    # ignore - maybe the next importer will have it.
	} elsif ($ex = &catch('W3C::Util::MalformedUriException')) {
	    &throw($self->badResource($ex->getUri));
	} elsif ($ex = &catch('W3C::Http::HttpMessageException')) {
	    &throw($ex->getHttpMessage->getBody); # it's already formatted nicely by a lower level
	} else {&throw()}}
    }
    return $added;
}

sub invokeImporter {
    my ($self, $importer, $aclDB, $resourceList, $wildcard, $recursive) = @_;
    return $importer->getAclsFor($aclDB, $resourceList, $wildcard, $recursive);
}

sub editAcls {
    my ($self) = @_;
    if ($self->{SUBMIT} != $SUBMIT_M_Picks && $self->{SUBMIT} != $SUBMIT_M_DelBin) {
	return;
    }
    my @edits;

    # handle resource deletes first
    foreach my $resource (@{$self->{DELETE_RESOURCES_NUKE}}) {
	eval {
	    $self->{ACL_REPOSITORY}->deleteAclFor([$resource]);
	}; if ($@) {if (my $ex = &catch('W3C::Util::NoSuchResourceException')) {
	} else {
	    die $@;
	}}
	# re: $self->{RESOURCE_LIST} = [grep {$_ ne $resource} @{$self->{RESOURCE_LIST}}];
	#  I do this after the ACLs are rendered so their deletion can be displayed
	$self->{ACL_DB}->delRule($resource, undef, undef, 0);
	$self->{ACL_DB}->addResourceAttribute($resource, {'status', 'deleted'});
	$self->{ACL_DB}->deleteResourceAttribute($resource, 'source');
	$self->{SOURCE}{$resource} = $SOURCE_chacl;
    }

    # process bin deletes if any are present
    # these are constructed in importCGIParms:
    # <INPUT TYPE="submit" NAME="w3c_delete_bin_B0_user_eric" VALUE="X"></font></li>
    # ($param =~ m/(w3c_delete_bin_([^_]+)_([^_]+)_(.*))/) && (push(@{$self->{DELETE_BINS}{$2}{$3}}, $4));
    foreach my $bagId (keys %{$self->{DELETE_BINS}}) {
	my $byType = $self->{DELETE_BINS}{$bagId};

	# find nodes with the identifier EXTERNAL_ID#bagId
	my @resources = $self->{ACL_DB}->getResourcesWithAttribute('aclBinBag', $self->{EXTERNAL_ID}.'#'.$bagId);
	foreach my $type (keys %$byType) {
	    my $ids = $byType->{$type};
	    foreach my $id (@$ids) {
		foreach my $resource (@resources) {
		    my $editString = '<b>'.$type.' '.$id.'</b> access to "<b>'. $resource.'</b>".';
		    my $thisAccess = $self->getEffectiveAccess($self->{ACL_DB}, $resource);
		    if (($thisAccess&$ACCESS_CHACL) == $ACCESS_CHACL) {
			$self->{ACL_DB}->delRule($resource, &buildDBRuleId2($type, $id), undef);
			$self->{SOURCE}{$resource} = $SOURCE_chacl;
			push (@edits, 'Take away '.$editString);
		    } else {
			push (@edits, 'You have insufficient rights to take away '.$editString);
		    }
		}
	    }
	}
    }

    # add rules from pick lists
    foreach my $pickId (sort keys %{$self->{PICK_TYPES}}) {
	foreach my $type (sort keys %{$self->{PICK_TYPES}{$pickId}}) {
	    my $ids = $self->{PICK_TYPES}{$pickId}{$type};
	    my $resources = $self->{PICK_RESOURCES}{$pickId};
	    my @accesses = &normalizeAccessStringList($self->{PICK_ACCESS}{$pickId});
	    foreach my $id (@$ids) {
		foreach my $resource (@$resources) {
		    my $editString = '<b>'.$type.' '.$id.' '.@accesses.'</b> access to "<b>'. $resource.'</b>".';
		    my $thisAccess = $self->getEffectiveAccess($self->{ACL_DB}, $resource);
		    if (($thisAccess&$ACCESS_CHACL) == $ACCESS_CHACL) {
			if (defined $self->{PICK_ACCESS}{$pickId}) {
			    # add the rule, replacing any existing rules for that id
			    $self->{ACL_DB}->addRule($resource, &buildDBRuleId2($type, $id), 
						     [@accesses], undef, 1);
			} else {
			    $self->{ACL_DB}->delRule($resource, &buildDBRuleId2($type, $id), undef);
			}
			push (@edits, 'Give '.$editString);
			$self->{SOURCE}{$resource} = $SOURCE_chacl;
		    } else {
			push (@edits, 'You have insufficient rights to give '.$editString);
		    }
		}
	    }
	}
    }

    # shortcut expensive discoverClientsInclusions if we haven't made changes
    return undef if (@edits < 1);
    $self->discoverClientsInclusions;
    return "<h3>Editing RDF code:</h3><ul>\n  <li>".join("</li>\n  <li>", @edits)."</li></ul>\n";
}

sub setNewAcls {
    my ($self) = @_;
    my (@creations, @updates, @deletes);
    my $reReadACLs = 0;

    my $ret = $self->editAcls;

    if ($self->{SUBMIT} != $SUBMIT_C_RDF && 
	$self->{SUBMIT} != $SUBMIT_C_Delete && 
	$self->{SUBMIT} != $SUBMIT_M_Copy && 
	!defined $self->{SUBMIT_ACL_MACRO}) {
	return $ret;
    }

    # now the rest ...
    my %resourceBins = $self->{ACL_DB}->getResourceBins;
    foreach my $bin (keys %resourceBins) {
	# check access for first URI in bin
	my $thisAccess = $self->getEffectiveAccess($self->{ACL_DB}, $resourceBins{$bin}[0][0]);
	next if (($thisAccess&$ACCESS_CHACL) != $ACCESS_CHACL);
	my $acl = undef;
	if ($self->{SUBMIT} == $SUBMIT_C_Delete) {
	    foreach my $resource (@{$resourceBins{$bin}[0]}) {
		eval {
		    $self->{ACL_REPOSITORY}->deleteAclFor([$resource], \@deletes);
		}; if ($@) {if (my $ex = &catch('W3C::Util::NoSuchResourceException')) {
		} else {
		    die $@;
		}}
		# re: $self->{RESOURCE_LIST} = [grep {$_ ne $resource} @{$self->{RESOURCE_LIST}}];
		#  I do this after the ACLs are rendered so their deletion can be displayed
		$self->{ACL_DB}->delRule($resource, undef, undef, 0);
		$self->{ACL_DB}->addResourceAttribute($resource, {'status', 'deleted'});
		$self->{ACL_DB}->deleteResourceAttribute($resource, 'source');
		$self->{SOURCE}{$resource} = $SOURCE_chacl;
	    }
	    $reReadACLs = 1;
	} elsif ($self->{SUBMIT} == $SUBMIT_C_RDF || $self->{SUBMIT} == $SUBMIT_M_Copy) {
	    $acl = $resourceBins{$bin}[1]; # [1] is an array of rules for this bin
	} elsif (defined $self->{SUBMIT_ACL_MACRO}) {
	    $acl = $self->{SUBMIT_ACL_MACRO};
	}
	if ($acl) {
	    # $ret .= "would have set acl for:"; # !!!
	    # $ret .= join(' ', @{$resourceBins{$bin}[0]}).' to '.$acl."<br />\n";
	    eval {
		$self->{ACL_REPOSITORY}->setAclFor($resourceBins{$bin}[0], $acl, \@creations, \@updates);
		$self->notify();
	    }; if ($@) {
		my $message;
		if (my $ex = &catch('W3C::Util::Exception')) {
		    $message = $ex->toString
		} else {
		    $message = $@;
		}
		$ret .= "No Worky! <pre>$message</pre>";
	    }
	    $reReadACLs = 1;
	}
    }

    if (@deletes > 0) {
	($ret .= 'Deleting entry(s) for "'.join('", "', @deletes)."\".<br />\n");
    }
    if (@creations > 0) {
	($ret .= 'Creating new entry(s) for "'.join('", "', @creations)."\".<br />\n");
    }
    if (@updates > 0) {
	($ret .= 'Updating entry(s) for '.join(', ', map {"<a href=\"$_\">$_</a>"} @updates).".<br />\n");
    }
    if ($reReadACLs) {
	# refresh to confirm new ACLs
	$self->{ACL_DB}->clearResources;
	$self->importAcls($self->{ACL_DB}, $self->{RESOURCE_LIST}, undef, $self->{WILDCARD} eq $recursiveWildcard);
	$self->discoverClientsInclusions;
    }
    return $ret;
}

sub notify {
    my ($self) = @_;
    my $to = $self->{PROPERTIES}->getI($PROP_NOTIFY_TO);
    my $from = $self->{PROPERTIES}->getI($PROP_NOTIFY_FROM);
    my $subj = $self->{PROPERTIES}->getI($PROP_NOTIFY_SUBJ);
    my $lastUpdate = $self->{ACL_REPOSITORY}->getMaxResourceUpdate();

    if (defined $to && defined $lastUpdate) {

	my $SENDMAIL = '/usr/lib/sendmail';
	$subj =~ s/\%d\b/$lastUpdate/g;
	my $pid = open(KID_TO_WRITE, "|-");
	$SIG{ALRM} = sub {
	    &throw(new W3C::Util::Exception(-message=>"mail pipe broke"));
	};

	if ($pid) {  # parent
	    print KID_TO_WRITE "To: $to\n";
	    print KID_TO_WRITE "From: $from\n";
	    print KID_TO_WRITE "Subject: $subj\n";
	    print KID_TO_WRITE "\n";
	    print KID_TO_WRITE "$MAIL_NOTIFY_BODY\n";
	    close(KID_TO_WRITE) || warn "kid exited $?";

	} else {     # child
	    if (!exec($SENDMAIL, '-t', "-f$from")) {
		&throw(new W3C::Util::Exception(-message => 
						"can't exec $SENDMAIL: $!"));
	    }
	}
    }
}

# discoverClientsInclusions - Find which rules in $aclDB include
#			      the $user and $ipAddr.

sub discoverClientsInclusions {
    my ($self) = @_;
    $self->{ACL_REPOSITORY}->setAuthResAttrs($self->{ACL_DB}, $self->{READ}->remote_user, $self->{READ}->remote_addr);
    ($self->{MAX_ACCESS}, $self->{MIN_ACCESS}) = $self->{ACL_DB}->setAccessResourceAttributes();
}

sub renderRenderOptions {
    my ($self) = @_;
    my $ret = "<!-- display options -->\n<p class=\"displayOptions\">View ACLs as\n  ";
    if ($ENV{REQUEST_METHOD} eq 'POST') {
	$ret .= $self->{WRITE}->popup_menu(-name=>'w3c_display',
					   -values=>\@DISPLAY_TYPE_LIST,
					   -default=>$self->{DISPLAY} & ($DISPLAY_DIFFS|$DISPLAY_LIST),
					   -labels=>\%DISPLAY_CONST_TO_STR);
	$ret .= '  in ';
	$ret .= $self->{WRITE}->checkbox(-name=>'w3c_display', 
					 -label=>'tabular form', 
					 -value=>$DISPLAY_TABLE, 
					 -checked=>$self->{DISPLAY} & $DISPLAY_TABLE);
	$ret .= '  and ';
	$ret .= $self->{WRITE}->checkbox(-name=>'w3c_display', 
					 -label=>'expert mode', 
					 -value=>$DISPLAY_EXPERT, 
					 -checked=>$self->{DISPLAY} & $DISPLAY_EXPERT);
	$ret .= '  . ';
	$ret .= $self->submitButton($SUBMIT_M_RefreshList);
	$ret .= "\n</p>";
    } else {
	my @ret  = "<!-- display options -->\n<p class=\"displayOptions\">View ACLs as\n  ";
	if (($self->{DISPLAY} & $DISPLAY_DIFFS) == $DISPLAY_DIFFS) {
	    my $link = $self->linkBack('View as list?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.(($self->{DISPLAY} ^ $DISPLAY_DIFFS) | $DISPLAY_LIST)]);
	    push (@ret, "Now viewing diffs. $link");
	} elsif (($self->{DISPLAY} & $DISPLAY_LIST) == $DISPLAY_LIST) {
	    my $link = $self->linkBack('View as diffs?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.(($self->{DISPLAY} ^ $DISPLAY_LIST) | $DISPLAY_DIFFS)]);
	    push (@ret, "Now viewing list. $link");
	} else {
	    &throw(new W3C::Util::ProgramFlowException());
	}
	push (@ret, "<br />\n");
	if (($self->{DISPLAY} & $DISPLAY_TABLE) == $DISPLAY_TABLE) {
	    my $link = $self->linkBack('View in linear mode?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.($self->{DISPLAY} ^ $DISPLAY_TABLE)]);
	    push (@ret, "Now viewing table. $link");
	} else {
	    my $link = $self->linkBack('View as table?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.($self->{DISPLAY} | $DISPLAY_TABLE)]);
	    push (@ret, "Now viewing in linnear mode. $link");
	}
	push (@ret, "<br />\n");
	if (($self->{DISPLAY} & $DISPLAY_EXPERT) == $DISPLAY_EXPERT) {
	    my $link = $self->linkBack('View in prosaic mode?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.($self->{DISPLAY} ^ $DISPLAY_EXPERT)]);
	    push (@ret, "Now viewing in hard-to-use mode. $link");
	} else {
	    my $link = $self->linkBack('View in hard-to-use mode?', 
				       $self->{ORIGINAL_RESOURCE}, 
				       [@{$self->wildcardParm(undef)}, 
					'w3c_display='.($self->{DISPLAY} | $DISPLAY_EXPERT)]);
	    push (@ret, "Now viewing in prosaic mode. $link");
	}
	$ret = join ('', @ret);
	$ret .= "\n</p>";
    }
    return "$ret\n";
}

sub renderACLs {
    my ($self, $standardMacro) = @_;
    my ($aclDesc, $displayed);
    my $ret = '';
    $aclDesc = $self->standardMacroToDesc($standardMacro);

    my $renderer = ($self->{DISPLAY} & $DISPLAY_TABLE) ? 
	new chacl::Renderer::WideTable($self, $self->{RESOURCE_BASE}) : 
	    new chacl::Renderer::List($self, $self->{RESOURCE_BASE});
    if (($self->{DISPLAY} & $DISPLAY_DIFFS) && defined !$aclDesc) {
	$renderer->start;
	$self->renderACLsAsDiffs($renderer, $self->{READ}->remote_user);
	$displayed = 1;
	$renderer->end;
	$ret .= $renderer->show;
    }
    if (($self->{DISPLAY} & $DISPLAY_LIST) || 
	(!defined $aclDesc && !($self->{DISPLAY} & $DISPLAY_TABLE) && !$displayed)) {
	$renderer->start;
	$ret .= "<h2>List of working ACLs:</h2>\n";
	$self->renderACLsAsList($renderer, $self->{ACL_DB});
	$renderer->end;
	$ret .= $renderer->show;
    }
#    if ($self->{DISPLAY} & $DISPLAY_TABLE) {
#	$ret .= "<h2>Table of working ACLs:</h2>\n";
#	$ret .= $self->renderACLsAsTable;
#    }
    return $ret;
}

#####
# renderACLsAsDiffs - display ACL differences from the closest pre-defined macro

sub renderACLsAsDiffs {
    my ($self, $renderer, $user) = @_;
    my %resourceBins = $self->{ACL_DB}->getResourceBins;
    my %binOrders;
    foreach my $bin (keys %resourceBins) {
	# sort resources in this bin by directory
	$binOrders{$bin} = [sort {$a =~m/\/$/ ?$b =~m/\/$/ ?$a cmp $b :-1 :$b=~m/\/$/ ?1 :$a cmp $b} @{$resourceBins{$bin}[0]}];
    }
    # sort bins by first directory entry
    my @byFirstDir = sort {$binOrders{$a}->[0] cmp $binOrders{$b}->[0]} keys %resourceBins;
    foreach my $bin (@byFirstDir) {

	# rehash the resources in this bin by type and name
	my $byType = {};
	foreach my $dbEntry (@{$resourceBins{$bin}[1]}) {
	    my ($type, $name, $access) = &parseDBRule($dbEntry);
	    $byType->{$type}{$name} = $access;
	}

	# make a copy of the AclDB with only the resources in $bin
	my $dbCopy = $self->{ACL_DB}->copy($resourceBins{$bin}[0]);

	# represent the ACLs in $dbCopy
	$self->renderDiff($renderer, $dbCopy, $byType, $resourceBins{$bin}[0], $user);
    }
    if ($self->{DISPLAY} & $DISPLAY_TABLE) {
    }
}

sub renderDiff {
    my ($self, $renderer, $aclCheckDB, $byType, $resources, $user) = @_;

    my ($acl, $diffs, $userMatches, $ruleName) = $self->getRuleInfo($aclCheckDB, $user, $byType, $resources);

    # use renderACLsAsList to render the rules remaining in diffs
    my @remainingRules = $diffs->rulesMatching([$resources->[0]], undef, undef);
    my $thisAccess = $self->getEffectiveAccess($aclCheckDB, $resources->[0]); # get access for first URI in bin
    my $canRacl = ($thisAccess&$ACCESS_RACL)==$ACCESS_RACL;
    my $canChacl = ($thisAccess&$ACCESS_RACL)==$ACCESS_RACL;
    my $accessStatus = $canChacl ? 1 : $canRacl ? 2 : 3;
    if ($self->{DISPLAY} & $DISPLAY_RESOURCE_FIRST) {
	$self->renderResourceList($renderer, $resources, $aclCheckDB, $thisAccess);
    }

    # render the name of the best match group
    $renderer->startRules;
    if (@remainingRules < 1) {
	if ($acl == -1) {
	    $renderer->addRules(' no access.');
	} else {
	    $renderer->renderRules($accessStatus, $userMatches, $ruleName, '.', $self->{ANNOTATE});
	}
    } else {
#	$renderer->addRules($ruleName." access plus:") if ($acl != -1);
#	$renderer->renderRules($accessStatus, $userMatches, $acl == -1 ? 'no' : $ruleName); # $acl == -1 ? ':' : ' '.$ruleName.' access plus:', $self->{ANNOTATE});
	if ($acl == -1) {
#	    $renderer->addRules(':');
	} else {
	    $renderer->renderRules($accessStatus, $userMatches, $ruleName, ' plus:', $self->{ANNOTATE});
	}
	if ($canRacl) {
	    my $aclBinBag = $aclCheckDB->getResourceAttribute($resources->[0], 'aclBinBag');
	    $self->renderAccessList($renderer, \@remainingRules, $aclCheckDB, $ENV{'REQUEST_URI'}, $aclBinBag, $canChacl);
	} else {
	    # @@@ may choose not to display
	    $renderer->addRules(' unreadable access.');
	}
    }
    $renderer->endRules;
    $self->renderResourceList($renderer, $resources, $aclCheckDB, $thisAccess) if (!($self->{DISPLAY} & $DISPLAY_RESOURCE_FIRST));
}

sub renderACLsAsList {
    my ($self, $renderer, $aclListDB) = @_;
    my $selfUrl = $ENV{'REQUEST_URI'};
    my %resourceBins = $aclListDB->getResourceBins;
    my %binOrders;
    foreach my $bin (keys %resourceBins) {
	$binOrders{$bin} = [sort {$a =~m/\/$/ ?$b =~m/\/$/ ?$a cmp $b :-1 :$b=~m/\/$/ ?1 :$a cmp $b} @{$resourceBins{$bin}[0]}];
    }
    foreach my $bin (sort {$binOrders{$a}->[0] cmp $binOrders{$b}->[0]} keys %resourceBins) {
	my $thisAccess = $self->getEffectiveAccess($aclListDB, $resourceBins{$bin}[0][0]); # get access for first URI in bin
	my $canRacl = ($thisAccess&$ACCESS_RACL)==$ACCESS_RACL;
	my $canChacl = ($thisAccess&$ACCESS_RACL)==$ACCESS_RACL;
	$self->renderResourceList($renderer, $resourceBins{$bin}[0], $aclListDB, $thisAccess) if ($self->{DISPLAY} & $DISPLAY_RESOURCE_FIRST);
	$renderer->startRules;
	$renderer->addRules(':');
	if ($canRacl) {
	    my $aclBinBag = $aclListDB->getResourceAttribute($resourceBins{$bin}[0][0], 'aclBinBag');
	    $self->renderAccessList($renderer, $resourceBins{$bin}[1], $aclListDB, $selfUrl, $aclBinBag, $canChacl);
	}
	$renderer->endRules;
	$self->renderResourceList($renderer, $resourceBins{$bin}[0], $aclListDB, $thisAccess) if (!($self->{DISPLAY} & $DISPLAY_RESOURCE_FIRST));
    }
}

sub renderResourceList {
    my ($self, $renderer, $resources, $aclListDB, $thisAccess) = @_;

    my @sortedResources = sort {$a =~ m/\/$/ ? $b =~ m/\/$/ ? $a cmp $b : -1 : $b =~ m/\/$/ ? 1 : $a cmp $b} @$resources;
    # sort {($a =~ m/\/$/)^($b =~ m/\/$/) ? 2*($b =~ m/\/$/)-1 : $a cmp $b;} would be extra geeky/cryptic

    # default to not requiring the user to select resources. (need to set this or it will inherit value from
    # last CGI call and will continue to REQUIRE_SELECTED after the user has waned down to one resource.)
    if (($thisAccess & $ACCESS_CHACL) == $ACCESS_CHACL) {
	map {$self->renderResource($renderer, $_, $aclListDB, 1, 1)} @sortedResources;
    } elsif (($thisAccess & $ACCESS_RACL) == $ACCESS_RACL) {
	map {$self->renderResource($renderer, $_, $aclListDB, 2, 1)} @sortedResources;
    } else { # @@@ may choose not to display
	map {$self->renderResource($renderer, $_, $aclListDB, 3, 0)} @sortedResources;
    }
}

sub renderResource {
    my ($self, $renderer, $resource, $aclListDB, $access, $href) = @_;
    my $source = $aclListDB->getResourceAttribute($resource, 'source');
    my $status = $aclListDB->getResourceAttribute($resource, 'status');

    my $statusClass = $self->{SOURCE_TO_CLASS}{$source};
    my $sourceLink = $self->{SOURCE_TO_STRING}{$source};
    if (defined $source) {
	if ($source == $SOURCE_chacl) {
	    if (defined $self->{SOURCE}{$resource}) {
		$source = $self->{SOURCE}{$resource};
	    }
	} else {
	    $self->{SOURCE}{$resource} = $source;
	}
    }
    my ($select, $rResource, $rScan, $rDetail, $rStatus);
    # we need a checkbox for the resource if the user has chacl access and there is more than one resource listed
    if ($access == 1 && @{$self->{RESOURCE_LIST}} > 1) { # don't think I want this: && $status ne 'deleted') {
	$select = 1;
	$select = 3 if ($self->{SELECTED}{$resource});
	$self->{REQUIRE_SELECTED} = 1;
    }
    if ($status ne 'deleted') {
	if ($access == 1 && $resource =~ m/\/\Z/ && $resource ne $self->{DIRECTORY_SCAN}) {
	    my $wildcard = $self->{WILDCARD} || '*';
	    $rScan .= $self->linkBack('scan ', $resource.$wildcard, $self->wildcardParm('*'))."\n";
	}
	if ($access == 1) {
	    $rDetail .= "<input type=\"submit\" name=\"w3c_detail_resource_$resource\" value=\"detail\">";
	}
    }
    if (defined $sourceLink) {
	$rStatus .= ' (from '.$sourceLink.')';
    }
    if ($status) {
	$rStatus .= ' (status: '.$status.')';
    }
    $renderer->renderResource($select, $resource, $href, $access, $rScan, $rDetail, $rStatus, $statusClass);
}

sub renderAccessList {
    my ($self, $renderer, $rules, $aclListDB, $selfUrl, $aclBinBag, $canChacl) = @_;

    $renderer->startAccess;
    foreach my $dbEntry (@$rules) {
	my ($color, $detail, $delete);
	my ($type, $name, $accessBits) = &parseDBRule($dbEntry);
	$type = $TYPE_TEXT{$type};
#	$color = !$aclListDB->passed($dbEntry) ? 'black' :
#	    $dbEntry->access =~ m/chacl/ ? 'green' : 
#		$dbEntry->access =~ m/racl/ ? '#eebb00' : 
#		    'red';
	my $access = ($accessBits & $ACCESS_CHACL) == $ACCESS_CHACL ? 1 :
	    ($accessBits & $ACCESS_RACL) == $ACCESS_RACL ? 2 : 3;
	my $binStr = 'bin_'.$aclBinBag.'_';
	my $idString = "w3c_detail_$binStr${type}_$name";
	$detail = "<input type=\"submit\" name=\"$idString\" value=\"detail\" />";
	if ($canChacl) {
	    $delete = "<input type=\"submit\" name=\"$idString\" value=\"delete\" />";
	}
	$renderer->renderAccess($type, $name, $access, $aclListDB->passed($dbEntry), $dbEntry, $detail, $delete, $self);
    }
    $renderer->endAccess;
}

#####
# getByAccess - use a Hash presenter to extract the rules from aclDB

sub getByAccess {
    my ($self) = @_;
    my $aclPresenter = new W3C::Rnodes::HashAclPresenter($self->{ACL_REPOSITORY});
    $self->{ACL_DB}->present($aclPresenter);
    return $aclPresenter->getHash;
}

sub renderACLsAsTable {
    my ($self) = @_;
    # convert to olde form form (a hash of form {resource}{access}{type}{id})
    my $byAccess = $self->getByAccess()->{$self->{RESOURCE_LIST}};
    my $ret = '';
    my $selfUrl = $self->{READ}->url.'?w3c_resource='.&CGI::escape($self->{RESOURCE_LIST});
    $ret .=<<EOF
<table summary="@@@" border="0" width="100%">
<tr><td></td><td bgcolor="#c0c0c0">group</td><td bgcolor="#c0c0c0">org</td>
<td bgcolor="#c0c0c0">ip</td><td bgcolor="#c0c0c0">user</td></tr>
EOF
    ;
    foreach my $access (sort keys %$byAccess) {
	my $byType = $byAccess->{$access};
	my $accessUri = '&w3c_detail_access='.$access;
	my @accessStrings = &accessStringList($access);
	$ret .= '<tr>';
	$ret .= '<td align=right bgcolor="#c0c0c0">'.@accessStrings.'</td>';
	foreach my $type (@DISPLAY_TYPES) {
	    $ret .= '<td>';
	    my $byId = $byType->{$type};
	    foreach my $id (sort keys %$byId) {
		my $descrip = $byId->{$id};
		my $listEntry = $id;
		$listEntry .= ' '.$descrip if (defined $descrip && $descrip ne '');
		my $thisOb = '&w3c_detail_'.$type.'='.$id;
		$ret .= "<a href=\"$selfUrl$accessUri$thisOb\">$listEntry</a><br />\n";
	    }
	    $ret .= '</td>'."\n";
	}
	$ret .= '<td>';
	my $tmp = $selfUrl.$accessUri.'&w3c_detail_'.join('&w3c_detail_', sort keys %$byType);
	$ret .= "<a href=\"$tmp\">show</a>";
	$ret .= '</td><td>';
	$ret .= "<input type=\"submit\" name=\"w3c_clear_access_$access\" VALUE=\"clear\" />";
	$ret .= '</td>';
	$ret .= '</tr>';
	$ret .= "<br />\n";
    }
    $ret .= '</table>'."\n";
    return $ret;
}

sub submitButton {
    my ($self, $submitId, $valueExtra) = @_;
    my $value = $SUBMIT_NAMES{$submitId}.$valueExtra;
    return "<input type=\"submit\" name=\"w3c_submit\" value=\"$value\" />";
}

sub getRuleInfo {
    my ($self, $aclCheckDB, $user, $byType, $resources) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub renderMacros {
    my ($self, $expertMode) = @_;
    my $ret = '';
    $ret .= "<!-- macro buttons for instance ACLs -->\n<table summary=\"Layout table: the first row is a series of buttons representing different macros, the secod row is a button to delete ACLs, and the optional third row is a button to read the input from the RDF presented in expert mode.\" border=\"0\">\n";

    my ($width, $macros) = $self->renderLocalMacros();
    $ret .= $macros;

    $ret .= "  <tr><td colspan=\"$width\" align=\"center\">\n        ";
    $ret .= $self->submitButton($SUBMIT_C_Delete)."</td></tr>\n";

    if ($expertMode) {
	$ret .= "  <tr><td colspan=\"3\" align=\"center\">\n        ";
	$ret .= $self->submitButton($SUBMIT_C_RDF)."</td></tr>\n";
    }

    $ret .= "</table>\n";
    return $ret;
}

sub renderLocalMacros {
    my ($self, $expertMode) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub getStandardMacro {
    my ($self, $aclDB, $user) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub standardDescToMacro {
    my ($self, $macroName) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub standardMacroToDesc {
    my ($self, $macroId) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub standardMacroToIcon {
    my ($self, $macroId) = @_;
    # pure virtual
    &throw(new W3C::Util::NotImplementedException());
}

sub renderAclRdf {
    my ($self) = @_;
    my $escapedRdf = $self->{WRITE}->escapeHTML($self->{ACL_RDF});
    my $sReview = $self->submitButton($SUBMIT_M_Review);
    my $sRevert = $self->submitButton($SUBMIT_M_Revert);
    my $sCopy = $self->submitButton($SUBMIT_M_Copy);
    my $sMore = $self->submitButton($SUBMIT_S_More);
    my $lMore = $self->{WRITE}->checkbox_group(-name => 'w3c_pick_moreList', -values => \@DISPLAY_TYPES2, -defaults => []);

    return <<EOF
  <!-- first the RDF input area -->	
  <textarea name="w3c_rdf_acl" rows="13" cols="100">$escapedRdf</textarea><br />

  <!-- and some buttons to process its contents -->
  $sReview | $sRevert
  $sCopy <input type="text" name="w3c_copyFrom" /><br />
  $sMore $lMore
EOF
    ;
}

sub renderPicks {
    my ($self) = @_;
    my ($ret, $detailedRows) = ('', 0);

    # repeat already rendered picks
    foreach my $pickId (sort keys %{$self->{PICK_TYPES}}) {
	my $byType = $self->{PICK_TYPES}{$pickId};
	my $resources = $self->{PICK_RESOURCES}{$pickId};
	next if (@$resources < 1);
	foreach my $type (sort keys %$byType) {
	    my $ids = $byType->{$type};
	    foreach my $id (@$ids) {
		my $ruleId =  &buildDBRuleId2($type, $id);
		my $thisAccess = ($self->{ACL_DB}->rulesMatching([$resources->[0]], $ruleId, undef))[0]->access;
		my @defaultAccesses = split(',', $thisAccess);
		my %types;
		foreach my $dbEntry ($self->{ACL_DB}->rulesMatching([$resources->[0]], undef, $thisAccess)) {
		    my ($entryType, $entryId, $entryAccess) = &parseDBRule2($dbEntry);
		    push (@{$types{$entryType}}, $entryId) if ($entryType eq $type); # @@@ limit to one picklist for $type
		}
		# foreach my $extraType (@{$self->{PICK_MORE_LIST}{$pickId}}) {
		#     push (@{$types{$extraType}}, undef)
		# }
		$self->{NEXT_PICK_ID} = $pickId + 1 if ($self->{NEXT_PICK_ID} <= $pickId);
		$ret .= $self->buildPickListSet($pickId, [sort @{$resources}], \%types, \@defaultAccesses, 10);
		$detailedRows++;
	    }
	}
    }

    # render freshly picked bin_detail buttons
    foreach my $binBag (sort keys %{$self->{DETAIL_BINS}}) {
	my $byType = $self->{DETAIL_BINS}{$binBag};

	# find nodes with the identifier EXTERNAL_ID#bagId
	my $resources = [$self->{ACL_DB}->getResourcesWithAttribute('aclBinBag', $self->{EXTERNAL_ID}.'#'.$binBag)];
	foreach my $type (sort keys %$byType) {
	    my $ids = $byType->{$type};
	    foreach my $id (@$ids) {
		my $ruleId =  &buildDBRuleId2($type, $id);
		my $thisAccess = ($self->{ACL_DB}->rulesMatching([$resources->[0]], $ruleId, undef))[0]->access;
		my @defaultAccesses = split(',', $thisAccess);
		my %types;
		foreach my $dbEntry ($self->{ACL_DB}->rulesMatching([$resources->[0]], undef, $thisAccess)) {
		    my ($entryType, $entryId, $entryAccess) = &parseDBRule2($dbEntry);
		    push (@{$types{$entryType}}, $entryId) if ($entryType eq $type); # @@@ limit to one picklist for $type
		}
		# foreach my $extraType (@{$self->{PICK_MORE_LIST}{'bin'}{$binBag}}) {
		#     push (@{$types{$extraType}}, undef)
		# }
		my $pickId = $self->{NEXT_PICK_ID}++; # 'bin_'.$binBag
		$ret .= $self->buildPickListSet($pickId, [sort @{$resources}], \%types, \@defaultAccesses, 10);
		$detailedRows++;
	    }
	}
    }

    # render freshly picked resource_detail buttons
    foreach my $resource (@{$self->{DETAIL_RESOURCES}}) {
	my %byAccess;
	foreach my $dbEntry ($self->{ACL_DB}->rulesMatching([$resource], undef, undef)) {
	    my ($entryType, $entryId, $entryAccess) = &parseDBRule2($dbEntry);
	    push (@{$byAccess{$entryAccess}{$entryType}}, $entryId); # @@@ limit to one picklist for $access
	}
	foreach my $access (sort keys %byAccess) {
	    my @defaultAccesses = split(',', $access);
	    my $byType = $byAccess{$access};
	    # foreach my $type (sort keys %$byType) {
	    #     my $ids = $byType->{$type};
	    #     foreach my $id (@$ids) {
	    #         my $ruleId =  &buildDBRuleId2($type, $id);
	    #         my $thisAccess = ($self->{ACL_DB}->rulesMatching([$resource], $ruleId, undef))[0]->access;
	    #         my @defaultAccesses = split(',', $thisAccess);
	    #         my $pickId = $self->{NEXT_PICK_ID}++; # 'bin_'.$binBag
	    #         $ret .= $self->buildPickListSet($pickId, [$resource], @@@, \@defaultAccesses, 10);
	    #         $detailedRows++;
	    #      }
	    # }
	    my $pickId = $self->{NEXT_PICK_ID}++; # 'bin_'.$binBag
	    $ret .= $self->buildPickListSet($pickId, [$resource], $byType, \@defaultAccesses, 10);
	    $detailedRows++;
	}
    }

    # render extra types that the user has selected to fill in
    if ($self->{SUBMIT} == $SUBMIT_S_More && @{$self->{PICK_MORE_LIST}} > 0) {
	my %types;

	# create arrays of default selections for each type to be rendered
	foreach my $extraType (@{$self->{PICK_MORE_LIST}}) {
	    $types{$extraType} = [];
	}

	my $pickId = $self->{NEXT_PICK_ID}++; # 'bin_'.$binBag
	$ret .= $self->buildPickListSet($pickId, [sort @{$self->{RESOURCE_LIST}}], \%types, [], 10);
	$detailedRows++;
    }

    return undef if (!$ret);

    # submit buttons for these picks
    if ($detailedRows > 0) {
	my $sPicks = $self->submitButton($SUBMIT_M_Picks);
	$ret .= <<EOF

<!-- button to accept the changes to the above picklists-->
<p class="submitPicks">
  $sPicks <input type="reset" />
</p>
EOF
    ;
    }

    my $tmp = ($self->{SUBMIT} == $SUBMIT_S_More ? 'Add' : 'Replace');
    return "<h2>$tmp access:</h2>\n$ret\n";
}

sub buildPickListSet {
    my ($self, $tableId, $resourceList, $types, $defaultAccesses, $height) = @_;
    my $ret = '';
    my @notDetailed = grep {!exists $types->{$_}} @DISPLAY_TYPES2;
    # my $tableWidth = 4 + 2*(keys %$types) + (@notDetailed > 0); # 4 <td>s + 2 for each $type + one for type options

    # command sentences consist of ...
    $ret .= "<table summary=\"Layout table: the table is used to vertically align the elements in the columns.\">\n  <tr>";
    $ret .= "    <td>Grant</td>\n" if (@notDetailed < @DISPLAY_TYPES2);
    # ... ids
    foreach my $type (sort keys %$types) {
	my @names;
	$self->{ACL_REPOSITORY}->getIds($type, \@names);
	$ret .= "    <td>$type</td>\n    <td>\n    <!-- select ${type} to whom to grant access -->\n";
	$ret .= $self->{WRITE}->scrolling_list(-name => 'w3c_pick_type_'.$tableId.'_'.$type, 
					       -values => \@names, -defaults => $types->{$type}, 
					       -size => $height, -multiple => 'true');
	$ret .= "    </td>\n";
    }
    my @accessOptions = map {$ACCESS_TEXT{$_}} @ACCESS_LIST;
    # ... their access
    $ret .= "    <td>\n    <!-- select access level to grant -->\n";
    $ret .= $self->{WRITE}->scrolling_list(-name, 'w3c_pick_access_'.$tableId, 
					   -values => \@accessOptions, 
					   -defaults => $defaultAccesses, 
					   -size => $height, -multiple => 'true');
    $ret .= "    </td>\n";
							#    if (@notDetailed < @DISPLAY_TYPES2);
    # ... target (scope of edit)
    $ret .= "    <td>access&nbsp;to</td>\n";
    my $resourceListHeight = @$resourceList < 1 ? 1 : @$resourceList > 10 ? 10 : @$resourceList;
    $ret .= "    <td>\n    <!-- select resources for which access is being granted -->\n";
    $ret .= $self->{WRITE}->scrolling_list(-name => 'w3c_pick_resources_'.$tableId, 
					   -values => $resourceList, -defaults => \@$resourceList, 
					   -size => $resourceListHeight, -multiple => 'true');
    $ret .= "    </td>\n";

    # ... selection additional types to detail
    # if (@notDetailed > 0) {
    #    $ret .= '<td>'.$self->submitButton($SUBMIT_S_More)."<br />\n";
    #	 $ret .= $self->{WRITE}->checkbox_group(-name => 'w3c_pick_moreList_'.$tableId, 
    #						-values => \@notDetailed, 
    #						-defaults => [], -linebreaks => 'true').'</td>';
    # }
    $ret .= "  </tr>\n</table>\n";
    return $ret;
}

sub renderDetails {
    my ($self) = @_;
    my $ret = '';
    foreach my $bin (sort keys %{$self->{DETAIL_BINS}}) {
	foreach my $detailGroup (@{$self->{DETAIL_BINS}{$bin}{'group'}}) {
	    if (defined $detailGroup && $detailGroup ne '') {
		my @hierarchy = $self->{ACL_REPOSITORY}->groupHierarchy($detailGroup);
		$ret .= "<hr /><h2>Group hierarchy for $detailGroup:</h2>\n";
		foreach my $element (@hierarchy) {
		    if ($element eq $detailGroup) {
			$ret .= "<em>$element</em>";
		    } else {
			$ret .= $element;
		    }
		    $ret .= "<br />\n";
		    $ret .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|<br />\n";
		}
	    }
	}

	foreach my $detailUser (@{$self->{DETAIL_BINS}{$bin}{'user'}}) {
	    if (defined $detailUser && $detailUser ne '') {
		my $detailString = $self->{ACL_REPOSITORY}->detailUser($detailUser);
		$ret .= "<hr /><h2>User details for $detailUser:</h2>\n";
		$ret .= "$detailString<br />\n";
	    }
	}

	foreach my $detailOrg (@{$self->{DETAIL_BINS}{$bin}{'org'}}) {
	    if (defined $detailOrg && $detailOrg ne '') {
		my ($detailString, $ips) = $self->{ACL_REPOSITORY}->detailOrg($detailOrg);
		$ret .= "<hr /><h2>Org details for $detailOrg:</h2>\n";
		$ret .= "$detailString<br />\n";
		foreach my $ip (@$ips) {
		    $ret .= "&nbsp;&nbsp;&nbsp;&nbsp;$ip<br />\n";
		}
	    }
	}

	foreach my $detailIp (@{$self->{DETAIL_BINS}{$bin}{'ip'}}) {
	    if (defined $detailIp && $detailIp ne '') {
		my $detailString = $self->{ACL_REPOSITORY}->detailIp($detailIp);
		$ret .= "<hr /><h2>Ip details for $detailIp:</h2>";
		$ret .= "$detailString<br />\n";
	    }
	}
    }
    return $ret;
}

sub standardReplyNUKE {
    my ($self, $status, $title, $heading, $blurb, $fields) = @_;
    $fields ||= {};

    my $sessionId = $self->{READ}->getSessionId;
    my $redirResponse = $self->{WRITE}->header({Status => $status, 
						Connection => 'close', 
						'Content-Type' => 'text/html; charset=iso-8859-1', 
						$SESSION_HEADER => $sessionId, 
						%$fields});
    $redirResponse .= <<EOF
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>$title</title>
      <!-- Session-Id: $sessionId -->
   </head>
   <body bgcolor="#FFFFFF" text="#000000" link="#0000ee">
      <h1>$heading</H1>
      <p>$blurb<p>
<address>
  $SESSION_LABEL: $sessionId<br />
  <a href="http://validator.w3.org/check/referer"><img
     src="http://validator.w3.org/images/vh40" height=31 width=88
     align=right border=0 alt="Valid HTML 4.0!"></a>
  $MAINTAINER<br />
  $REVISION
</address>
   </body>
</html>
EOF
    ;
    return $redirResponse;
}

sub standardHttpMessage {
    my ($self, $status, $title, $heading, $blurb, $fields) = @_;
    $fields ||= {};
    my $sessionId = $self->{READ}->getSessionId;
    my @headers = ([Connection => 'close'], 
		   ['Content-Type' => 'text/html; charset=iso-8859-1'], 
		   [$SESSION_HEADER => $sessionId]);
    foreach my $field (keys %$fields) {
	push (@headers, [$field, $fields->{$field}]);
    }

    my $body = <<EOF
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>$title</title>
      <!-- Session-Id: $sessionId -->
   </head>
   <body bgcolor="#FFFFFF" text="#000000" link="#0000ee">

      <h1>$heading</H1>
      <p>$blurb<p>
<address>
  $SESSION_LABEL: $sessionId<br />
  <a href="http://validator.w3.org/check/referer"><img
     src="http://validator.w3.org/images/vh40" height=31 width=88
     align=right border=0 alt="Valid HTML 4.0!"></a>
  $MAINTAINER<br />
  $REVISION
</address>
   </body>
</html>
EOF
    ;

    return new W3C::Http::HttpMessageException(-statusCode => $status, -headers => \@headers, -body => $body);
}

sub redirect {
    my ($self, $to, $from) = @_;
    my $title = '302 Moved Temporarily';
    my $body = 'Squirrely little bastard got away this time.';
    return $self->standardHttpMessage(302, $title, $title, $body, {'Location' => $to});
}

sub badResource {
    my ($self, $resource) = @_;
    my $title = 'ChAcl: Bad Resource';
    my $body = "chacl was unable to find resource \"$resource\".";
    return $self->standardHttpMessage(200, $title, $title, $body, {});
}

sub noMapsForURI {
    my ($self, $agent, $host, $uri) = @_;
    my $title = 'ChAcl: No Maps For URI';
    if (!defined $agent) {
	$agent = 'chacl';
    }
    my $body = "$agent could not find a map for host \"$host\" and URI \"$uri\".";
    return $self->standardHttpMessage(200, $title, $title, $body, {});
}

sub emptyAclDatabase {
    my ($self, $previousMessages) = @_;
    my $title = 'ChAcl: No Selected Resources';
    if (!$self->{WILDCARD}) {
	$self->{WILDCARD} = '*';
    }
    if ($self->{ORIGINAL_RESOURCE} =~ m/\Q$self->{WILDCARD}\E/) {
    } else {
	$self->{ORIGINAL_RESOURCE} .= $self->{WILDCARD};
    }
    my $try = $self->getSelfUri;
    my $body = <<EOF
<p>The operation you requested had no resources on which to operate.<br />
Perhaps the resource you selected does not exist, or you need to 
<a href='Javascript:history.go(-1)'>go back</a> and  select the resources
you want to act on.</p>

<p>You may want to try <a href="$try">$try</a>.</p>
EOF
    ;
    if ($previousMessages) {
	$body .= "<h1>Likely Causes</h1>\n<pre>$previousMessages</pre>";
    }
    return $self->standardHttpMessage(404, $title, $title, $body, {});
}

sub confFileMissing {
    my ($self, $filename) = @_;
    my $title = 'ChAcl: Configuration File Missing';
    my $body = "chacl needs the file \"$filename\" to operate.";
    return $self->standardHttpMessage(200, $title, $title, $body, {});
}

sub parseException {
    my ($self, $rdf, $message) = @_;
    $rdf = $self->{WRITE}->escapeHTML($rdf);
    my $title = 'ChAcl: Parse Exception';
    my $body = "chacl was unable to parse the supplied RDF.<br /><pre>$message</pre>in<pre>$rdf</pre>";
    return $self->standardHttpMessage(200, $title, $title, $body, {});
}

sub unknownException {
    my ($self, $exception, $when) = @_;
    if (ref $exception && $exception->isa('W3C::Util::Exception')) {
	$exception = $exception->toString;
    }
    my $text = $self->{WRITE}->escapeHTML($exception);
    my $title = "ChAcl: Unknown Exception$when";
    my $body = "chacl blew its mind.<br /><pre>$exception</pre>";
    return $self->standardHttpMessage(500, $title, $title, $body, {});
}

sub requireAuth {
    my ($self, $realm, $needed) = @_;
    my $resource = "<ul>\n";
    map {$resource .= "<li>\n<a href=\"".$_."\">".$_."</a></li>\n"} @{$self->{RESOURCE_LIST}};
    $resource .= "</ul>\n";
    my $title = 'Authorization Required';
    my $body = "You need $needed access to these resources to perform the requested operation.";
    return $self->standardHttpMessage(401, $title, $title, $body, {'WWW-Authenticate' => "Basic realm=\"$realm\""});
}

#####
# Front end to W3C::Util::ShowSource module

sub showMySources {
    my ($query) = @_;
    my $sessionId = $query->getSessionId;
    use W3C::Util::ShowSource;
    my @sources = ('chaclCGI.pl', 'W3C::Database::DBIInterface.pm', 
		   'W3C::Rnodes::ACL.pm', 'W3C::Rnodes::W3CAclAgent.pm', 'W3C::Rnodes::UnixPermAgent.pm', 
		   'W3C::Rnodes::RdfAclAgent.pm', 
		   'W3C::Util::Properties.pm', 'W3C::Util::ShowSource.pm', 'W3C::Rdf::RdfParser.pm', 
		   'W3C::SAX::XmlParser.pm', 'W3C::SAX::InputSource.pm', 'W3C::SAX::AttributeListImpl.pm', 
		   'W3C::SAX::HandlerBase.pm', 'W3C::SAX::SAXException.pm', 'W3C::SAX::SAXParseException.pm', 
		   'W3C::SAX::HandlerBase.pm', 'W3C::SAX::XmlElement.pm');
    my %paths = ('chaclCGI.pl' => 'chaclCGI', 
		 'W3C::Rnodes::ACL.pm' => 'ACL', 
		 'W3C::Rnodes::W3CAclAgent.pm' => 'W3CAclAgent', 
		 'W3C::Rnodes::UnixPermAgent.pm' => 'UnixPermAgent', 
		 'W3C::Rnodes::RdfAclAgent.pm' => 'RdfAclAgent', 
		 'W3C::Database::DBIInterface.pm' => 'DBIInterface',
		 'W3C::Util::Properties.pm' => 'Properties', 
		 'W3C::Util::ShowSource.pm' => 'ShowSource', 
		 'W3C::Rdf::RdfParser.pm' => 'RdfParser', 
		 'W3C::SAX::XmlParser.pm' => 'XmlParser', 
		 'W3C::SAX::InputSource.pm' => 'InputSource', 
		 'W3C::SAX::AttributeListImpl.pm' => 'AttributeListImpl', 
		 'W3C::SAX::HandlerBase.pm' => 'HandlerBase', 
		 'W3C::SAX::SAXException.pm' => 'SAXException', 
		 'W3C::SAX::SAXParseException.pm' => 'SAXParseException', 
		 'W3C::SAX::HandlerBase.pm' => 'HandlerBase', 
		 'W3C::SAX::XmlElement.pm' => 'XmlElement');

    my ($useColor, $funcLinks, $funcList) = ($query->param('w3c_useColor'), 
					     $query->param('w3c_funcLinks'), 
					     $query->param('w3c_funcList'));
    #####
    # Start the page
    print $query->header(({-expires => '+4h', $SESSION_HEADER => $query->getSessionId}));
#    print $query->start_html(-title=>'ACL Editor Sources', -BGCOLOR=>"#FFFFFF", -TEXT=>"#000000", -LINK=>"#0000ee", -VLINK=>"#551a8b");
    print <<EOHEADER
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>ACL Editor Sources</title>
      <!-- Session-Id: $sessionId -->
      <link href="http://www.w3.org/StyleSheets/Core/chaclCGI.css" rel="stylesheet" type="text/css">
   </head>
   <body bgcolor="#FFFFFF" text="#000000" link="#0000ee">
EOHEADER
    ;
    print <<EOF;
<h1>ACL Editor Sources</h1>
    <p>
    <a href='#chaclCGI'>chaclCGI</a> presents the the current 
    ACLs for a resource and creates forms to manipulate them. 
    <a href='#chaclCGI'>chaclCGI</a> then handles 
    the output from the forms. Both are layered on top of 
    <a href='#W3CAclAgent'>W3C::Rnodes::W3CAclAgent.pm</a> and 
    <a href='#DBIInterface'>W3C::Database::DBIInterface.pm</a>.
    <p>
    RDF parsing facilities are handled by a stack of parser:
    <pre>
    <a href='#RdfParser'>W3C::Rdf::RdfParser</a>
       |
    <a href='#XmlParser'>W3C::SAX::XmlParser</a>
    </pre> The <a href='#XmlParser'>W3C::SAX::XmlParser</a> is an
    implementation of a SAX interface ported to perl. It uses:
    <ul>
	<li><a href='#InputSource'>W3C::SAX::InputSource</a>
	<li><a href='#AttributeListImpl'>W3C::SAX::AttributeListImpl</a>
	<li><a href='#HandlerBase'>W3C::SAX::HandlerBase</a>
	<li><a href='#SAXException'>W3C::SAX::SAXException</a>
	<li><a href='#SAXParseException.'>W3C::SAX::SAXParseException.</a>
	<li><a href='#HandlerBase'>W3C::SAX::HandlerBase</a>
	<li><a href='#XmlElement'>W3C::SAX::XmlElement</a>
    </ul>
    <p>
    &lt;brought to you by <a href='#ShowSource'>W3C::Util::ShowSource.pm</a>&gt;.
EOF
    ;

    #####
    # Print source
    my $showSource = W3C::Util::ShowSource->new(\@sources, \%paths, $query);
    $showSource->present($useColor, $funcLinks, $funcList);
    print <<EOBOTTOM
<address>
  $SESSION_LABEL: $sessionId<br />
  <a href="http://validator.w3.org/check/referer">not <img
     src="http://validator.w3.org/images/vh40" height=31 width=88
     align=right border=0 alt="Valid HTML 4.0!"></a>
  $MAINTAINER<br />
  $REVISION
</address>
EOBOTTOM
    ;
    print $query->end_html."\n";
}

#####
# handy page to prompt for resource to edit

sub resourcePrompt {
    my ($self, $query) = @_;
    my $selfUri = $self->getSelfPath();
    my $sessionId = $query->getSessionId;
#    my $link = $selfUri.'?w3c_special=showSource';
#    my ($useColor, $funcLinks, $funcList) = ($query->checkbox('w3c_useColor', 'use color'), 
#					     $query->checkbox('w3c_funcLinks'), 
#					     $query->checkbox('w3c_funcList'));

    #####
    # Start the page
    print $query->header({$SESSION_HEADER => $query->getSessionId});
#    print $query->start_html(-title=>'W3C Acl Editor', -BGCOLOR=>"#ffffff", -TEXT=>"#000000", -LINK=>"#0000ee"), "\n";
    print <<EOHEADER
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
   <head>
      <title>W3C ACL Editor</title>
      <!-- Session-Id: $sessionId -->
      <link href="http://www.w3.org/StyleSheets/Core/chaclCGI.css" rel="stylesheet" type="text/css">
   </head>
   <body bgcolor="#FFFFFF" text="#000000" link="#0000ee">
EOHEADER
    ;
#    print '<link href="/StyleSheets/Core/base.css" rel="stylesheet" type="text/css">'."\n";
#<head>
#  <title>W3C ACL Editor</title>
#  <link rev=\"made\" href=\"mailto:eric@w3.org\">
#  <meta name=\"keywords\" content=\"ACL, access control list, resource access,
#    W3C ACL Editor\">
#  <meta name=\"description\" content=\"Edit W3C's ACLs for a given resource.\">
#</head>

    print <<EOF;
  <p>
    <a href=\"http://www.w3.org/\"><img
       src=\"http://www.w3.org/Icons/WWW/w3c_home\" height=48 border=0
       alt=\"W3C\"></a>
  </p>
  <h1>W3C ACL Editor</h1>
  <p>
    Welcome to the W3C ACL Editor!
  </p>
  <p>
    This tool manipulates access control lists for a given resource.
  </p>
  <hr />
  <h2><a name=\"byUri\">Edit ACLs by URI</a></h2>
  <p>
    Enter the URI of a document you would like to secure. It must be in a 
    form like:</p>
<pre>http://www.w3.org/Library/Overview.html
</pre>
  <form method=GET action=\"$selfUri\">
    URI: <input name=\"w3c_resource\" size="50"> Wildcard: <input type="input" name="w3c_wildcard" value="*" size="3"><br />
    <input type=\"submit\" value=\"Get ACLs details for this URI\">
    <input type=\"reset\" value=\"Reset this form\">
  </form>
  <hr />
  <h2><a name=\"otherInfo\">Other sources of information</a></h2>
  <ul>
    <li>
	<form method=GET action=\"$selfUri\">
	Source code browser:<br />
	<INPUT TYPE="checkbox" NAME="w3c_useColor" VALUE="*"> <font color=\"red\">i</font><font color=\"orange\">n</font> <font color=\"yellow\">c</font><font color=\"green\">o</font><font color=\"blue\">l</font><font color=\"purple\">o</font><font color=\"red\">r</font> (about 2.5 times as large)<br />
	<INPUT TYPE="checkbox" NAME="w3c_funcLinks" VALUE="*"> list of functions with links to their declarations<br />
	<INPUT TYPE="checkbox" NAME="w3c_funcList" VALUE="*"> links function from function invocations (a cool tool, but it takes about 10 minutes to generate)<br />
	<input name=\"w3c_showSource\" type=\"submit\" value=\"see the source code\">
	</form>
	</li>
  </ul>
  <h2><a name=\"toDo\">To Do</a></h2>
  <ul>
    <li>A user manager.
  </ul>
EOF
    ;
    print <<EOBOTTOM
<hr />
<address>
  $SESSION_LABEL: $sessionId<br />
  <a href="http://validator.w3.org/check/referer"><img
     src="http://validator.w3.org/images/vh40" height=31 width=88
     align=right border=0 alt="Valid HTML 4.0!"></a>
  $MAINTAINER<br />
  $REVISION
</address>
EOBOTTOM
    ;
    print $query->end_html."\n";
}

package chacl::Renderer;
sub new {
    my ($proto, $chacl, $base) = @_;
    my $class = ref $proto || $proto;
    my $self = {};
    bless ($self, $class);
    $self->{CHACL} = $chacl;
    $self->{BASE} = $base;
    return $self;
}
sub show {my ($self) = @_; return $self->{TEXT};}
sub accessMarkup {
    my ($self, $access, $userMatches) = @_;
    my $class = ($access == 1 ? 'readWrite' : $access == 2 ? 'readOnly' : $access == 3 ? 'noAccess' : 'yellow');
    my $color = $userMatches ? ($access == 1 ? 'green' : $access == 2 ? 'blue' : $access == 3 ? 'black' : 'yellow') : 'black';
    return ($class, $color);
}
sub renderResource {
    my ($self, $select, $resource, $href, $access, $rScan, $rDetail, $rStatus, $statusClass) = @_;
    my ($rResource, $rSelect, $base) = ('', '', $self->{BASE});
    my $name;
    if ($select & 1) {
	$name = 'w3c_select_'.W3C::Util::W3CDebugCGI::escapeName($resource);
	push (@{$self->{CHACL}{BUTTON_NAMES}}, $name);
	$rSelect .= '<input type="checkbox" name="'.$name.'" id="'.$name.'"';
	$rSelect .= ' checked="checked"' if ($select & 2);
	$rSelect .= ' />';
    }
    my ($class, $color) = $self->accessMarkup($access, 1);

    $rResource .= '<label for="'.$name.'">' if ($name);
    $rResource .= '<a class="'.$class.'" href="'.$resource.'">' if ($href);
    if ($resource =~ m/^\Q$base\E (.*) $ /x) { # @@@ !!!
	$rResource .= $1 ? $1 : '&lt;the directory&gt;'; # counts on $base being a directory
    } else {
	$rResource .= $resource;
    }
    $rResource .= '</a>' if ($href);
    $rResource .= '</label>' if ($name);
    $self->addResource($resource, $rSelect, $rResource, $rScan, $rDetail, $rStatus, $statusClass);
}
sub renderAccess {
    my ($self, $type, $name, $access, $userMatches, $dbEntry, $detail, $delete, $mapper) = @_;
    my $accessString = $dbEntry->accessStr;
    my ($class, $color) = $self->accessMarkup($access, $userMatches);
    my $rAccess = '';
    $rAccess .= $userMatches ? 
	'<span title="This rule applies to you">&#x2282;</span>' : 
	'<span title="This rule does not apply to you">&#x2284;</span>';
    if (my $source = $dbEntry->getRuleAttribute('source')) {
	my $statusClass = $mapper->{SOURCE_TO_CLASS}{$source};
	my $sourceLink = $mapper->{SOURCE_TO_STRING}{$source};
	$rAccess .= "<span $statusClass>$sourceLink</span>";
    }
    $rAccess .= "<a class=\"$class\">";
    $rAccess .= $type eq 'all' && $name eq 'all' ? 
	"<b>everyone</b> has <b>$accessString</b> access." : 
	"$type <b>$name</b> has <b>$accessString</b> access.";
    $rAccess .= '</a>';
    $self->addAccess($rAccess, $detail, $delete);
}
sub renderRules {
    my ($self, $access, $userMatches, $rule, $suffix, $addHrefs) = @_;
    my ($class, $color) = $self->accessMarkup($access, $userMatches);
    my $rRule = ' ';
    $rRule .= $userMatches ? 
	'<span title="This rule applies to you">&#x2282;</span>' : 
	'<span title="This rule does not apply to you">&#x2284;</span>';
    $rRule .= '<a class="'.$class.'">';
    $rRule .= '<a href="#'.$rule.'-access">' if ($addHrefs);
    $rRule .= $rule;
    $rRule .= '</a>' if ($addHrefs);
    $rRule .= ' access';
    $rRule .= '</a>';
    $rRule .= $suffix;
    $self->addRules($rRule);
}
sub start {my ($self) = @_; $self->{TEXT} = '';}
sub end {}
sub startRules {}
sub endRules {}
sub startAccess {}
sub endAccess {}

package chacl::Renderer::Table;
use W3C::Rnodes::ChaclConstants qw($DISPLAY_EXPERT);

sub start {
    my ($self) = @_;
    $self->SUPER::start;
    $self->{TEXT} .= "<table summary=\"Layout table: each row presents the ACLs for a different resource. Within each row, the first column may contain a button to select the resource, the second names and links to the resource, the third shows how the resource got its current ACLs, and the fourth column shows the ACLs for the resource.\" border=\"0\">\n";
}
sub end {my ($self) = @_; $self->{TEXT} .= "</table>\n";}
sub startRules {my ($self) = @_; $self->{TEXT} .= '<tr><td></td><th><b>ACL: </b></th><th colspan="3" align="left"><div class="groupHeading">';}
sub addRules {
    my ($self, @rules) = @_;
    $self->{TEXT} .= join (" ", @rules);
}
sub addResource {
    my ($self, $resource, $rSelect, $rResource, $rScan, $rDetail, $rStatus, $statusClass) = @_;
    $self->{TEXT} .= '<tr><td>'.$rSelect.'</td><td colspan="2">'.$rResource.'</td><td>'.$rScan;
    $self->{TEXT} .= '</td><td>'.$rDetail if ($self->{DISPLAY} & $DISPLAY_EXPERT);
    $self->{TEXT} .= "</td><td $statusClass>$rStatus" if ($rStatus);
    $self->{TEXT} .= '</td></tr>'."\n";
}
sub addAccess {
    my ($self, $rAccess, $detail, $delete) = @_;
    $self->{TEXT} .= "<br />\n".$rAccess;
    $self->{TEXT} .= ' '.$detail.' '.$delete if ($self->{DISPLAY} & $DISPLAY_EXPERT);
}
sub endRules {my ($self) = @_; $self->{TEXT} .= "</div></td></tr>\n";}

package chacl::Renderer::WideTable;
use W3C::Rnodes::ChaclConstants qw($DISPLAY_EXPERT);

sub startRules {my ($self) = @_; $self->{RULES} = '<div class="groupHeading">';}
sub addRules {
    my ($self, @rules) = @_;
    $self->{RULES} .= join (" ", @rules);
}
sub addAccess {
    my ($self, $rAccess, $detail, $delete) = @_;
    $self->{RULES} .= "<br />\n".$rAccess;
    $self->{RULES} .= ' '.$detail.' '.$delete if ($self->{DISPLAY} & $DISPLAY_EXPERT);
}
sub endRules {my ($self) = @_; $self->{RULES} .= "</div>";}
sub addResource {
    my ($self, $resource, $rSelect, $rResource, $rScan, $rDetail, $rStatus, $statusClass) = @_;
    $self->{BY_RESOURCE}{$resource} .=<<EOF
  <tr><!-- ACLs for $resource -->
    <td>
      $rSelect
    </td><td colspan="2">
      $rResource
    </td><td>
      $rScan
EOF
    ;
    if ($self->{DISPLAY} & $DISPLAY_EXPERT) {
	$self->{BY_RESOURCE}{$resource} .= "    </td><td>\n      $rDetail\n";
    }
    $self->{BY_RESOURCE}{$resource} .= "    </td><td $statusClass>\n      $rStatus\n" if ($rStatus);
    $self->{BY_RESOURCE}{$resource} .= "    </td><td align=\"left\">\n";
    $self->{BY_RESOURCE}{$resource} .= '      '.$self->{RULES};
    $self->{BY_RESOURCE}{$resource} .= "    </td>\n  </tr>\n";
}
sub end {
    my ($self) = @_;
    my @resources = (sort {$a =~m/\/$/ ?$b =~m/\/$/ ?
			       substr($a, 0, -1) cmp substr($b, 0, -1) :
				   -1 :
				       $b=~m/\/$/ ?1 :
					   $a cmp $b} keys %{$self->{BY_RESOURCE}});
    my @lines = ();
    foreach my $resource (@resources) {
	# $self->{TEXT} .= $self->{BY_RESOURCE}{$resource};
	push (@lines, $self->{BY_RESOURCE}{$resource});
    }
    # $self->{TEXT} .= "</table>\n";
    $self->{TEXT} .= join ("\n", @lines)."</table>\n";
}

package chacl::Renderer::List;
use W3C::Rnodes::ChaclConstants qw($DISPLAY_EXPERT);

sub start {
    my ($self) = @_;
    $self->SUPER::start;
    $self->{TEXT} .= "<dl>\n";
}
sub end {my ($self) = @_; $self->{TEXT} .= "</dl>\n";}
sub startRules {my ($self, @rules) = @_; $self->{TEXT} .= '<dt><dd><table summary="Layout table: the table is only used to align the contents of the columns."><tr><th><b>ACL:</b> </th><td><div class="groupHeading">';}
sub addRules {my ($self, @rules) = @_; $self->{TEXT} .= join ("<br />\n", @rules);}
sub addResource {
    my ($self, $resource, $rSelect, $rResource, $rScan, $rDetail, $rStatus, $statusClass) = @_;
    $rSelect .= ' ' if ($rSelect);
    $rDetail = undef if (!($self->{DISPLAY} & $DISPLAY_EXPERT));
    $self->{TEXT} .= $rSelect.$rResource;
    $self->{TEXT} .= '('.$rScan.$rDetail.')' if ($rScan || $rDetail);
    $self->{TEXT} .= "<div class=\"$statusClass\">$rStatus</div><br />\n";
}
sub startAccess {my ($self) = @_; $self->{TEXT} .= "";}
sub endRules {my ($self) = @_; $self->{TEXT} .= "</div></td></tr></table>\n";}
sub addAccess {
    my ($self, $rAccess, $detail, $delete) = @_;
    $self->{TEXT} .= "<br />\n".$rAccess;
    $self->{TEXT} .= ' ('.$detail.$delete.')' if ($self->{DISPLAY} & $DISPLAY_EXPERT);
}

#####
# W3C::Rnodes::HashAclPresenter - 
#    aclPresenter that translates W3C::Rnodes::AclDB rules to a
#    form specific to the W3C::Rnodes::W3CAclAgent.

package W3C::Rnodes::HashAclPresenter;
use W3C::Util::Exception;
use W3C::Rnodes::ACL qw(%TEXT_TYPE);

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self = {};
    $self->{ACL_REPOSITORY} = shift;
    $self->{BY_RESOURCE} = {};
    bless ($self, $class);
    return $self;
}
sub startDB {
    my ($self) = @_;
}
sub startResource {
    my ($self, $ruleVector, $resource) = @_;
}
sub addRule {
    my ($self, $ruleVector, $resource, $id, $access) = @_;
    my ($type, $nm) = &parseDBRuleId($id);
    &throw(new W3C::Util::Exception("bad format for rule \"$id\"")) if (!$nm);
    $type = $TEXT_TYPE{$type};
    $access = &accessBitFieldList($access);
    # maybe use this where clause: 'value=\"'.$id.'\"')
    my ($name, $descrip) = $self->{ACL_REPOSITORY}->getNameAndDescripByName($type, $nm);
    $self->{BY_RESOURCE}{$resource}{$access}{$type}{$name} = $descrip;
}
sub endResource {
    my ($self, $ruleVector, $resource) = @_;
}
sub endDB {
    my ($self) = @_;
    my $resource = $self->{RESOURCE};
}
sub getHash {
    my ($self) = @_;
    return $self->{BY_RESOURCE};
}

package W3C::Rnodes::AclRdfErrorHandler; # @@@ doubt it, but want to see where it's used
use W3C::Util::Exception;

# this error handler is general enough to handle SAXExceptions and RdfDBExceptions

sub new {
    my ($class, $documentHandler) = @_;
    my $proto = ref $class || $class;
    my $self = {};
    bless($self, $proto);
    $self->{-documentHandler} = $documentHandler;
    $self->{STRING} = '';
    return $self;
}

sub makeString {
    my ($self, $exception) = @_;
    my $ret = $exception->toString;
    if ($exception->isa('W3C::Rdf::RdfDBException')) {
	$ret .= ' at line '.$self->{-documentHandler}->getLineNumber.' column '.$self->{-documentHandler}->getColumnNumber;
    }
    return $ret;
}

sub warning {
    my ($self, $exception) = @_;
    $self->{STRING} .= 'warning: '.$self->makeString($exception)."\n";
}

sub error {
    my ($self, $exception) = @_;
    $self->{STRING} .= 'error: '.$self->makeString($exception)."\n";
    &throw($exception);
}

sub fatalError {
    my ($self, $exception) = @_;
    $self->{STRING} .= 'fatal error: '.$self->makeString($exception)."\n";
    &throw($exception);
}

sub toString {
    my ($self) = @_;
    return $self->{STRING};
}

package W3C::Rnodes::CGIChacl;

1;

__END__

=head1 NAME chacl

CGI::chacl - CGI interface for setting access to web resources

=head1 SYNOPSIS

  http://<server>/CGI/chacl

=head1 DESCRIPTION

The chacl script is called via the CGI interface from a webserver.

chacl enables the user to browse and set Access Control Lists (henceforth ACLs) via an HTTP CGI interface. With no parameters, chacl prompts the user for the resources for which to browse/change ACLs. With a set of resorces, chacl checks the list of ACL sources and displays the ACLs for those resources.

This module is used with the W3C::Rnodes CPAN module.

=head2 Authentication

Users actions are evaluated prior to execution and are only permitted if the requesting user has access to perform the requested function. For instance, before displaying ACLs for a resource, chacl makes sure that one of the following is true: everyone may see the ACL, the authenticated user may see the resource ACLs, or anyone connecting from the remote address may see the resource ACLs. Smilar check are performed when the user attempts to alter ACLs for a resource. The user may elect to change identity, even when it is not required for the desired action. The "Change Authentication" will cause chacl to require new authentication, which will cause the client to prompt the user for a new identity and password.

=head2 User Input

User input comes in four flavors: L<presentation|/"Presentation">, L<state selection|/"State selection">, L<state manipulation|/"State manipulation">, and L<commit|/"Commit">. state manipulation and commit require the user to select when modifying the ACLs for multiple resources at once. Javascript-enabled browsers will have extra buttons for manipulating the resourse selection.

=head2 User macros

The system is designed to allow the user to make streamlined ACL selections. These ACLs represent a common configuration that the user will use repleated. Asl the system administrator to create these macros.

=head2 RDF

The chacl system is based on RDF assertions of trust. Internal state is maintained by a block of RDF that is optionally hidden or editable depending on whether chacl is in "expert mode". This state is ephemoral and goes away unless the user commits the changes with the "Replace ACLs with RDF below" button.

=head2 Presentation

Several buttons control the chacl display. The options inlcude:

    help - display verbose help interspersed with the
           user prompts.
    list / diff - view ACLs as a comprehensive list or
                  as changes to the closest macro.
    tabular form - justify resources/ACLs by placing
                   output in a large table.
    expert mode - view the extended "expert" options.

=head2 State selection

In "expert" mode, the user may get lists of web ids for whom to add or remove ACLs.

=head2 State manipulation

Once the user has selected some web ids and ACLs in the "State selection" step, He/she must tell chacl to write these changes back to the RDF. This is done with the "Replace ACLs with RDF below" button.

=head2 Commit

Commit actions are ones that specifically write ACLs to the ACL repository. These include the User Macros as well as the Commit RDF Below button.

=head2 ACL Sources

chacl can get ACLs from a list of ACL agents, For instance, W3CAclAgent, UnixPermAgent, RdfAclAgent, and maybe, someday, the CVSAclAgent. These agents support the AclImportInterface, mainly, the getAclsFor method. One of these agents will also likely serve as the repository. The AclRepositoryInterface has methods to setAclsFor and deleteAclFor.

=head2 Architecture



=head1 AUTHOR

Eric Prud'hommeaux <eric@w3.org>

=head1 SEE ALSO

W3C::Rnodes::ACL(3) W3C::Rnodes::W3CAclAgent(3) W3C::Rnodes::UnixPermAgent(3) W3C::Rnodes::RdfAclAgent(3).

=cut

