ACLs: finish group edit (agents + rights) & browse
authorivan <ivan>
Sun, 18 Jun 2006 12:54:49 +0000 (12:54 +0000)
committerivan <ivan>
Sun, 18 Jun 2006 12:54:49 +0000 (12:54 +0000)
16 files changed:
FS/FS/AccessRight.pm
FS/FS/access_group.pm
FS/FS/access_groupagent.pm
FS/FS/m2name_Common.pm [new file with mode: 0644]
FS/FS/part_pkg.pm
FS/MANIFEST
htetc/handler.pl
httemplate/browse/access_group.html
httemplate/browse/access_user.html
httemplate/edit/access_group.html
httemplate/edit/elements/edit.html
httemplate/edit/part_pkg.cgi
httemplate/edit/process/access_group.html
httemplate/edit/process/elements/process.html
httemplate/elements/checkboxes-table-name.html [new file with mode: 0644]
httemplate/elements/checkboxes-table.html

index 01d63e3..5229e1e 100644 (file)
@@ -1,7 +1,7 @@
 package FS::AccessRight;
 
 use strict;
-user vars qw(@rights %rights);
+use vars qw(@rights); # %rights);
 use Tie::IxHash;
 
 =head1 NAME
@@ -19,59 +19,94 @@ assigned to users and/or groups.
 
 =cut
 
+#@rights = (
+#  'Reports' => [
+#    '_desc' => 'Access to high-level reporting',
+#  ],
+#  'Configuration' => [
+#    '_desc' => 'Access to configuration',
+#
+#    'Settings' => {},
+#
+#    'agent' => [
+#      '_desc' => 'Master access to reseller configuration',
+#      'agent_type'  => {},
+#      'agent'       => {},
+#    ],
+#
+#    'export_svc_pkg' => [
+#      '_desc' => 'Access to export, service and package configuration',
+#      'part_export' => {},
+#      'part_svc'    => {},
+#      'part_pkg'    => {},
+#      'pkg_class'   => {},
+#    ],
+#
+#    'billing' => [
+#      '_desc' => 'Access to billing configuration',
+#      'payment_gateway'  => {},
+#      'part_bill_event'  => {},
+#      'prepay_credit'    => {},
+#      'rate'             => {},
+#      'cust_main_county' => {},
+#    ],
+#
+#    'dialup' => [
+#      '_desc' => 'Access to dialup configuraiton',
+#      'svc_acct_pop' => {},
+#    ],
+#
+#    'broadband' => [
+#      '_desc' => 'Access to broadband configuration',
+#      'router'     => {},
+#      'addr_block' => {},
+#    ],
+#
+#    'misc' => [
+#      'part_referral'      => {},
+#      'part_virtual_field' => {},
+#      'msgcat'             => {},
+#      'inventory_class'    => {},
+#    ],
+#
+#  },
+#
+#);
+#
+##turn it into a more hash-like structure, but ordered via IxHash
+
+#well, this is what we have for now.  could be ordered better, could be lots of
+# things better, but this ACL system does 99% of what folks need and the UI
+# isn't *that* bad
 @rights = (
-  'Reports' => [
-    '_desc' => 'Access to high-level reporting',
-  ],
-  'Configuration' => [
-    '_desc' => 'Access to configuration',
-
-    'Settings' => {},
-
-    'agent' => [
-      '_desc' => 'Master access to reseller configuration',
-      'agent_type'  => {},
-      'agent'       => {},
-    ],
-
-    'export_svc_pkg' => [
-      '_desc' => 'Access to export, service and package configuration',
-      'part_export' => {},
-      'part_svc'    => {},
-      'part_pkg'    => {},
-      'pkg_class'   => {},
-    ],
-
-    'billing' => [
-      '_desc' => 'Access to billing configuration',
-      'payment_gateway'  => {},
-      'part_bill_event'  => {},
-      'prepay_credit'    => {},
-      'rate'             => {},
-      'cust_main_county' => {},
-    ],
-
-    'dialup' => [
-      '_desc' => 'Access to dialup configuraiton',
-      'svc_acct_pop' => {},
-    ],
-
-    'broadband' => [
-      '_desc' => 'Access to broadband configuration',
-      'router'     => {},
-      'addr_block' => {},
-    ],
-
-    'misc' => [
-      'part_referral'      => {},
-      'part_virtual_field' => {},
-      'msgcat'             => {},
-      'inventory_class'    => {},
-    ],
-
-  },
+  'New customer',
+  'View customer',
+  #'View Customer | View tickets',
+  'Edit customer',
+  'Cancel customer',
+  'Delete customer',
+
+  'Order customer package',
+  'Change customer package',
+  'Edit customer package dates',
+  'Customize customer package',
+  'Suspend customer package',
+  'Unsuspend customer package',
+  'Cancel customer package immediately',
+  'Cancel customer package later',
+
+  'Provision service',
+  'Unprovision service',
+  #legacy link stuff
+
+  'Post payment',
+  'Process payment',
+  'Post credit',
+  #more financial stuff
 
 );
 
-#turn it into a more hash-like structure, but ordered via IxHash
+sub rights {
+  @rights;
+}
 
index 9d870e5..2519040 100644 (file)
@@ -3,8 +3,11 @@ package FS::access_group;
 use strict;
 use vars qw( @ISA );
 use FS::Record qw( qsearch qsearchs );
+use FS::m2name_Common;
+use FS::access_groupagent;
+use FS::access_right;
 
-@ISA = qw(FS::Record);
+@ISA = qw(FS::m2m_Common FS::m2name_Common FS::Record);
 
 =head1 NAME
 
@@ -27,15 +30,14 @@ FS::access_group - Object methods for access_group records
 
 =head1 DESCRIPTION
 
-An FS::access_group object represents an example.  FS::access_group inherits from
+An FS::access_group object represents an access group.  FS::access_group inherits from
 FS::Record.  The following fields are currently supported:
 
 =over 4
 
 =item groupnum - primary key
 
-=item groupname - 
-
+=item groupname - Access group name
 
 =back
 
@@ -45,7 +47,7 @@ FS::Record.  The following fields are currently supported:
 
 =item new HASHREF
 
-Creates a new example.  To add the example to the database, see L<"insert">.
+Creates a new access group.  To add the access group to the database, see L<"insert">.
 
 Note that this stores the hash reference, not a distinct copy of the hash it
 points to.  You can ask the object for a copy with the I<hash> method.
@@ -84,7 +86,7 @@ returns the error, otherwise returns false.
 
 =item check
 
-Checks all fields to make sure this is a valid example.  If there is
+Checks all fields to make sure this is a valid access group.  If there is
 an error, returns the error, otherwise returns false.  Called by the insert
 and replace methods.
 
@@ -105,12 +107,51 @@ sub check {
   $self->SUPER::check;
 }
 
+=item access_groupagent
+
+Returns all associated FS::access_groupagent records.
+
+=cut
+
+sub access_groupagent {
+  my $self = shift;
+  qsearch('access_groupagent', { 'groupnum' => $self->groupnum } );
+}
+
+=item access_rights
+
+Returns all associated FS::access_right records.
+
+=cut
+
+sub access_rights {
+  my $self = shift;
+  qsearch('access_right', { 'righttype'   => 'FS::access_group',
+                            'rightobjnum' => $self->groupnum 
+                          }
+         );
+}
+
+=item access_right RIGHTNAME
+
+Returns the specified FS::access_right record.  Can be used as a boolean, to
+test if this group has the given RIGHTNAME.
+
+=cut
+
+sub access_right {
+  my( $self, $name ) = shift;
+  qsearchs('access_right', { 'righttype'   => 'FS::access_group',
+                             'rightobjnum' => $self->groupnum,
+                             'rightname'   => $name,
+                           }
+          );
+}
+
 =back
 
 =head1 BUGS
 
-The author forgot to customize this manpage.
-
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.
index 6b5def1..3de8fee 100644 (file)
@@ -3,6 +3,7 @@ package FS::access_groupagent;
 use strict;
 use vars qw( @ISA );
 use FS::Record qw( qsearch qsearchs );
+use FS::agent;
 
 @ISA = qw(FS::Record);
 
@@ -27,7 +28,7 @@ FS::access_groupagent - Object methods for access_groupagent records
 
 =head1 DESCRIPTION
 
-An FS::access_groupagent object represents an example.  FS::access_groupagent inherits from
+An FS::access_groupagent object represents an group reseller virtualization.  FS::access_groupagent inherits from
 FS::Record.  The following fields are currently supported:
 
 =over 4
@@ -47,7 +48,7 @@ FS::Record.  The following fields are currently supported:
 
 =item new HASHREF
 
-Creates a new example.  To add the example to the database, see L<"insert">.
+Creates a new group reseller virtualization.  To add the record to the database, see L<"insert">.
 
 Note that this stores the hash reference, not a distinct copy of the hash it
 points to.  You can ask the object for a copy with the I<hash> method.
@@ -86,7 +87,7 @@ returns the error, otherwise returns false.
 
 =item check
 
-Checks all fields to make sure this is a valid example.  If there is
+Checks all fields to make sure this is a valid group reseller virtualization.  If there is
 an error, returns the error, otherwise returns false.  Called by the insert
 and replace methods.
 
@@ -100,20 +101,29 @@ sub check {
 
   my $error = 
     $self->ut_numbern('groupagentnum')
-    || $self->ut_number('groupnum')
-    || $self->ut_number('agentnum')
+    || $self->ut_foreign_key('groupnum', 'access_group', 'groupnum')
+    || $self->ut_foreign_key('agentnum', 'agent',        'agentnum')
   ;
   return $error if $error;
 
   $self->SUPER::check;
 }
 
+=item agent
+
+Returns the associated FS::agent object.
+
+=cut
+
+sub agent {
+  my $self = shift;
+  qsearchs('agent', { 'agentnum' => $self->agentnum } );
+}
+
 =back
 
 =head1 BUGS
 
-The author forgot to customize this manpage.
-
 =head1 SEE ALSO
 
 L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/m2name_Common.pm b/FS/FS/m2name_Common.pm
new file mode 100644 (file)
index 0000000..7c9637e
--- /dev/null
@@ -0,0 +1,95 @@
+package FS::m2name_Common;
+
+use strict;
+use vars qw( @ISA $DEBUG );
+use FS::Schema qw( dbdef );
+use FS::Record qw( qsearch qsearchs ); #dbh );
+
+@ISA = qw( FS::Record );
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::m2name_Common - Base class for tables with a related table listing names
+
+=head1 SYNOPSIS
+
+use FS::m2name_Common;
+
+@ISA = qw( FS::m2name_Common );
+
+=head1 DESCRIPTION
+
+FS::m2name_Common is intended as a base class for classes which have a
+related table that lists names.
+
+=head1 METHODS
+
+=over 4
+
+=item process_m2name
+
+=cut
+
+sub process_m2name {
+  my( $self, %opt ) = @_;
+
+  my $self_pkey = $self->dbdef_table->primary_key;
+  my $link_sourcekey = $opt{'num_col'} || $self_pkey;
+
+  my $link_table = $self->_load_table($opt{'link_table'});
+
+  my $link_static = $opt{'link_static'} || {};
+
+  foreach my $name ( @{ $opt{'names_list'} } ) {
+
+    my $obj = qsearchs( $link_table, {
+        $link_sourcekey  => $self->$self_pkey(),
+        $opt{'name_col'} => $name,
+        %$link_static,
+    });
+
+    if ( $obj && ! $opt{'params'}->{"$link_table.$name"} ) {
+
+      my $d_obj = $obj; #need to save $obj for below.
+      my $error = $d_obj->delete;
+      die "error deleting $d_obj for $link_table.$name: $error" if $error;
+
+    } elsif ( $opt{'params'}->{"$link_table.$name"} && ! $obj ) {
+
+      #ok to clobber it now (but bad form nonetheless?)
+      #$obj = new "FS::$link_table" ( {
+      $obj = "FS::$link_table"->new( {
+        $link_sourcekey  => $self->$self_pkey(),
+        $opt{'name_col'} => $name,
+        %$link_static,
+      });
+      my $error = $obj->insert;
+      die "error inserting $obj for $link_table.$name: $error" if $error;
+    }
+
+  }
+
+  '';
+}
+
+sub _load_table {
+  my( $self, $table ) = @_;
+  eval "use FS::$table";
+  die $@ if $@;
+  $table;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+
index 05dc599..de4d047 100644 (file)
@@ -1,7 +1,7 @@
 package FS::part_pkg;
 
 use strict;
-use vars qw( @ISA %freq %plans $DEBUG );
+use vars qw( @ISA %plans $DEBUG );
 use Carp qw(carp cluck confess);
 use Tie::IxHash;
 use FS::Conf;
@@ -571,6 +571,32 @@ sub is_free {
   }
 }
 
+
+sub freqs_href {
+  #method, class method or sub? #my $self = shift;
+
+  tie my %freq, 'Tie::IxHash', 
+    '0'  => '(no recurring fee)',
+    '1h' => 'hourly',
+    '1d' => 'daily',
+    '1w' => 'weekly',
+    '2w' => 'biweekly (every 2 weeks)',
+    '1'  => 'monthly',
+    '2'  => 'bimonthly (every 2 months)',
+    '3'  => 'quarterly (every 3 months)',
+    '6'  => 'semiannually (every 6 months)',
+    '12' => 'annually',
+    '24' => 'biannually (every 2 years)',
+    '36' => 'triannually (every 3 years)',
+    '48' => '(every 4 years)',
+    '60' => '(every 5 years)',
+    '120' => '(every 10 years)',
+  ;
+
+  \%freq;
+
+}
+
 =item freq_pretty
 
 Returns an english representation of the I<freq> field, such as "monthly",
@@ -578,29 +604,14 @@ Returns an english representation of the I<freq> field, such as "monthly",
 
 =cut
 
-tie %freq, 'Tie::IxHash', 
-  '0'  => '(no recurring fee)',
-  '1h' => 'hourly',
-  '1d' => 'daily',
-  '1w' => 'weekly',
-  '2w' => 'biweekly (every 2 weeks)',
-  '1'  => 'monthly',
-  '2'  => 'bimonthly (every 2 months)',
-  '3'  => 'quarterly (every 3 months)',
-  '6'  => 'semiannually (every 6 months)',
-  '12' => 'annually',
-  '24' => 'biannually (every 2 years)',
-  '36' => 'triannually (every 3 years)',
-  '48' => '(every 4 years)',
-  '60' => '(every 5 years)',
-  '120' => '(every 10 years)',
-;
-
 sub freq_pretty {
   my $self = shift;
   my $freq = $self->freq;
-  if ( exists($freq{$freq}) ) {
-    $freq{$freq};
+
+  my $freqs_href = $self->freqs_href;
+
+  if ( exists($freqs_href->{$freq}) ) {
+    $freqs_href->{$freq};
   } else {
     my $interval = 'month';
     if ( $freq =~ /^(\d+)([hdw])$/ ) {
index 9e3285d..098fe4a 100644 (file)
@@ -342,3 +342,6 @@ t/access_right.t
 FS/m2m_Common.pm
 FS/pay_batch.pm
 t/pay_batch.t
+FS/ConfDefaults.pm
+t/ConfDefaults.t
+FS/m2name_Common.pm
index d400a55..1dfa137 100644 (file)
@@ -182,6 +182,10 @@ sub handler
       use FS::pkg_class;
       use FS::access_user;
       use FS::access_group;
+      use FS::access_usergroup;
+      use FS::access_groupagent;
+      use FS::access_right;
+      use FS::AccessRight;
 
       if ( %%%RT_ENABLED%%% ) {
         eval '
index 6ba89ea..9ebb2b8 100644 (file)
@@ -4,6 +4,45 @@ my $html_init =
   "Internal access groups control access to the back-office interface.<BR><BR>".
   qq!<A HREF="${p}edit/access_group.html"><I>Add an internal access group</I></A><BR><BR>!;
 
+#false laziness w/access_user.html & agent_type.cgi
+my $agents_sub = sub {
+  my $access_group = shift;
+
+  [ map {
+          my $access_groupagent = $_;
+          my $agent = $access_groupagent->agent;
+          [
+            {
+              'data'  => $agent->agent,
+              'align' => 'left',
+              'link'  => $p. 'edit/agent.cgi?'. $agent->agentnum,
+            },
+          ];
+        }
+    grep { $_->agent } #?
+    $access_group->access_groupagent,
+
+  ];
+  
+};
+
+my $rights_sub = sub {
+  my $access_group = shift;
+
+  [ map { my $access_right = $_;
+          [
+            { 
+              'data'  => $access_right->rightname,
+              'align' => 'left',
+            },
+          ];
+        }
+    $access_group->access_rights,
+
+  ];
+
+};
+
 my $count_query = 'SELECT COUNT(*) FROM access_group';
 
 my $link = [ $p.'edit/access_group.html?', 'groupnum' ];
@@ -22,12 +61,18 @@ my $link = [ $p.'edit/access_group.html?', 'groupnum' ];
                  'count_query' => $count_query,
                  'header'      => [ '#',
                                     'Group name',
+                                    'Agents',
+                                    'Rights',
                                   ],
                  'fields'      => [ 'groupnum',
                                     'groupname',
+                                    $agents_sub,
+                                    $rights_sub,
                                   ],
                  'links'       => [ $link,
                                     $link,
+                                    '',
+                                    '',
                                   ],
              )
 %>
index 38d5430..be11bf8 100644 (file)
@@ -4,7 +4,7 @@ my $html_init =
   "Internal users have access to the back-office interface.  Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees.  It is <B>highly recommended</B> to add a <B>separate account for each person</B> rather than using role accounts.<BR><BR>".
   qq!<A HREF="${p}edit/access_user.html"><I>Add an internal user</I></A><BR><BR>!;
 
-#false laziness w/agent_type.cgi
+#false laziness w/access_group.html & agent_type.cgi
 my $groups_sub = sub {
   my $access_user = shift;
 
index 11b8df7..d7f7667 100644 (file)
@@ -5,6 +5,42 @@
                                'groupnum'   => 'Group number',
                                'groupname'  => 'Group name',
                              },
+
                  'viewall_dir' => 'browse',
+
+                 'html_bottom' =>
+                   sub {
+                     my $access_group = shift;
+
+                     "<BR>Group virtualized to customers of agents:<BR>".
+                     ntable("#cccccc",2).
+                     '<TR><TD>'.
+                     include( '/elements/checkboxes-table.html',
+                                'source_obj'   => $access_group,
+                                'link_table'   => 'access_groupagent',
+                                'target_table' => 'agent',
+                                'name_col'     => 'agent',
+                                'target_link'  => $p.'edit/agent.cgi?',
+                                'disable-able' => 1,
+                            ).
+                     '</TR></TD></TABLE>'.
+
+                     "<BR>Group rights:<BR>".
+                     ntable("#cccccc",2).
+                     '<TR><TD>'.
+                     include( '/elements/checkboxes-table-name.html',
+                                'source_obj'   => $access_group,
+                                'link_table'   => 'access_right',
+                                'link_static'  => { 'righttype' =>
+                                                      'FS::access_group',
+                                                  },
+                                'num_col'      => 'rightobjnum',
+                                'name_col'     => 'rightname',
+                                'names_list'   => [ FS::AccessRight->rights() ],
+                            ).
+                     '</TR></TD></TABLE>'
+
+                     ;
+                   },
            )
 %>
index 120c03a..94bf6ee 100644 (file)
   #
   # 'menubar'     => '', #menubar arrayref
   #
+  # #run when re-displaying with an error
+  # 'error_callback' => sub { my $cgi, $object = @_; },
+  #
+  # #run when editing
+  # 'edit_callback' => sub { my $cgi, $object = @_; },
+  #
+  # #run when adding
+  # 'new_callback' => sub { my $cgi, $object = @_; },
+  #
+  # #broken'html_table_bottom' => '', #string or listref of additinal HTML to
+  #                            #add before </TABLE>
+  #
   # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
   #
   # 'html_bottom' => '', #string
       map { $_ => scalar($cgi->param($_)) } fields($table)
     });
 
+    &{$opt{'error_callback'}}($cgi, $object)
+      if $opt{'error_callback'};
+
   } elsif ( $cgi->keywords ) { #editing
 
     my( $query ) = $cgi->keywords;
     $query =~ /^(\d+)$/;
     $object = qsearchs( $table, { $pkey => $1 } );
 
+    &{$opt{'edit_callback'}}($cgi, $object)
+      if $opt{'edit_callback'};
+
   } else { #adding
 
     $object = $class->new( {} );
 
+    &{$opt{'new_callback'}}($cgi, $object)
+      if $opt{'new_callback'};
+
   }
 
   my $action = $object->$pkey() ? 'Edit' : 'Add';
index 462d516..b085d22 100755 (executable)
@@ -237,7 +237,7 @@ if ( dbdef->table('pkg_svc')->column('primary_svc') ) {
   push @form_radio, 'pkg_svc_primary';
 }
 
-tie my %freq, 'Tie::IxHash', %FS::part_pkg::freq;
+tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()};
 if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) {
   delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq;
 }
index e8c6d07..9bb9d1d 100644 (file)
@@ -1,5 +1,15 @@
 <%= include( 'elements/process.html',
                'table'       => 'access_group',
                'viewall_dir' => 'browse',
+               'process_m2m' => { 'link_table'   => 'access_groupagent',
+                                  'target_table' => 'agent',
+                                },
+               'process_m2name' => {
+                     'link_table'   => 'access_right',
+                     'link_static'  => { 'righttype' => 'FS::access_group', },
+                     'num_col'      => 'rightobjnum',
+                     'name_col'     => 'rightname',
+                     'names_list'   => [ FS::AccessRight->rights() ],
+               },
            )
 %>
index 59ad35e..a6e3b50 100644 (file)
   # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search'
   # 'process_m2m' => { 'link_table'   => 'link_table_name',
   #                    'target_table' => 'target_table_name',
-  #                  }.
+  #                  },
+  # 'process_m2name' => { 'link_table'   => 'link_table_name',
+  #                       'link_static' => { 'column' => 'value' },
+  #                       'num_col' => 'column', #if column name is different in
+  #                                              #link_table than source_table 
+  #                       'name_col' => 'name_column',
+  #                       'names_list' => [ 'list', 'names' ],
+  #                     },
 
   my(%opt) = @_;
 
                               );
   }
 
+  if ( !$error && $opt{'process_m2name'} ) {
+    $error = $new->process_m2name( %{ $opt{'process_m2name'} },
+                                   'params' => scalar($cgi->Vars),
+                                 );
+  }
+
   if ( $error ) {
     $cgi->param('error', $error);
     print $cgi->redirect(popurl(2). "$table.html?". $cgi->query_string );
diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html
new file mode 100644 (file)
index 0000000..8e9dd29
--- /dev/null
@@ -0,0 +1,85 @@
+<%
+
+  ##
+  # required
+  ##
+  # 'link_table'      => 'table_name',
+  #
+  # 'name_col' => 'name_column',
+  # #or
+  # 'name_callback' => sub { },
+  #
+  # 'names_list' => [ 'value', 'other value' ],
+  #
+  ##
+  # recommended (required?)
+  ##
+  # 'source_obj'   => $obj,
+  # #or?
+  # #'source_table' => 'table_name',
+  # #'sourcenum'    => '4', #current value of primary key in source_table
+  # #                       # (none is okay, just pass it if you have it)
+  ##
+  # optional
+  ##
+  # 'num_col' => 'col_name' #if column name is different in link_table than
+  #                         #source_table
+  # 'link_static' => { 'column' => 'value' },
+
+  my( %opt ) = @_;
+
+  my( $source_pkey, $sourcenum, $source_obj );
+  if ( $opt{'source_obj'} ) {
+
+    $source_obj = $opt{'source_obj'};
+    #$source_table = $source_obj->dbdef_table->table;
+    $source_pkey = $source_obj->dbdef_table->primary_key;
+    $sourcenum = $source_obj->$source_pkey();
+
+  } else {
+
+    #$source_obj?
+    $source_pkey = $opt{'source_table'}
+                     ? dbdef->table($opt{'source_table'})->primary_key
+                     : '';
+    $sourcenum = $opt{'sourcenum'};
+  }
+
+  $source_pkey = $opt{'num_col'} || $source_pkey;
+
+  my $link_static = $opt{'link_static'} || {};
+
+%>
+
+<% foreach my $name ( @{ $opt{'names_list'} } ) {
+
+     my $checked;
+     if ( $cgi->param('error') ) {
+
+       $checked = $cgi->param($opt{'link_table'}. ".$name" )
+                    ? 'CHECKED'
+                    : '';
+
+     } else {
+
+       $checked =
+         qsearchs( $opt{'link_table'}, {
+                                         $source_pkey     => $sourcenum,
+                                         $opt{'name_col'} => $name,
+                                         %$link_static,
+                                       }                                 )
+                    ? 'CHECKED'
+                    : ''
+
+     }
+
+%>
+
+  <INPUT TYPE="checkbox" NAME="<%= $opt{'link_table'}. ".$name" %>" <%= $checked %> VALUE="ON">
+
+  <%= $name %>
+
+  <BR>
+
+<% } %>
+
index d26ebef..16376fa 100644 (file)
    ) {
 
      my $targetnum = $target_obj->$target_pkey();
+
+     my $checked;
+     if ( $cgi->param('error') ) {
+
+       $checked = $cgi->param($target_pkey.$targetnum)
+                    ? 'CHECKED'
+                    : '';
+
+     } else {
+
+       $checked = qsearchs( $opt{'link_table'}, {
+                                                  $source_pkey => $sourcenum,
+                                                  $target_pkey => $targetnum,
+                                                }                             )
+                    ? 'CHECKED'
+                    : ''
+
+     }
+
 %>
 
-  <INPUT TYPE="checkbox" NAME="<%= $target_pkey. $targetnum %>" <%=
-        qsearchs( $opt{'link_table'}, {
-          $source_pkey => $sourcenum,
-          $target_pkey => $targetnum,
-        })
-          ? 'CHECKED '
-          : ''
-  %> VALUE="ON">
+  <INPUT TYPE="checkbox" NAME="<%= $target_pkey. $targetnum %>" <%= $checked %> VALUE="ON">
 
   <% if ( $opt{'target_link'} ) { %>