# $Id: Admin.pm,v 1.24 2004/10/05 21:53:31 gwolf Exp $
######################################
# Comas - Conference Management System
######################################
# Copyright 2003 CONSOL
# Congreso Nacional de Software Libre (http://www.consol.org.mx/)
#   Gunnar Wolf <gwolf@gwolf.cx>
#   Manuel Rabade <mig@mig-29.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
######################################

######################################
# Module: Comas::Admin
# Manage administrative tasks for a Comas database
######################################
# Depends on:
#
# Comas::Common - Common functions for various Comas modules
# Comas::Admin::admin_mgr - Manage Comas administrators
# Comas::Admin::person_cat_adm - Manage Comas person catalogs
# Comas::Admin::person_adm - Person's information administration
# Comas::Admin::proposal_cat_adm - Manage Comas proposal catalogs
# Comas::Admin::schedule_adm - Manage rooms and schedule administration
# Comas::Admin::monetary_adm - Manage money-related issues
# Comas::Admin::acadmic_committee - Setting status and scheduling of proposals
package Comas::Admin;

use strict;
use warnings;
use Carp;
use Comas::Common qw(valid_hash);
use Comas::Admin::admin_mgr qw(:adm :task :priv);
use Comas::Admin::person_cat_adm qw(:studies :person_type :state
				    :country :pers_status :query);
use Comas::Admin::person_adm qw(:lookup :modif :delete);
use Comas::Admin::proposal_cat_adm qw(:track :prop_status :prop_type);
use Comas::Admin::schedule_adm qw(:room :timeslot :timeslot_prop_type);
use Comas::Admin::monetary_adm qw(:exp_status :payment_type :time_discount
				  :price);
use Comas::Admin::academic_committee qw(:prop_modif :schedule);

###
### PENDING:
###
### I don't like how I am duplicating code (i.e. in delete_room and 
### delete_timeslot). DBI transactions cannto be nested. There must be a
### better way to do this than through duplication - maybe -as DBI's 
### documentation points out- using eval blocks... Let's think about it :-/
###

=head1 NAME

Comas::Admin - Manage administrative tasks for a Comas database

=head1 SYNOPSIS

  $adm = Comas::Admin->new(-db=>$db, -login=>$login, -passwd=>$passwd);

  $name = Comas::Admin->get_name(-db=>$db, -login=>$login);
  $name = Comas::Admin->get_name(-db=>$db, -id=>$id);
  $name = $adm->get_name(-login=>$login);
  $name = $adm->get_name(-id=>$id);
  $name = $adm->get_name;

  $ok = $adm->set_passwd($newpass);

  %tasks = Comas::Admin->get_tasks(-db=>$db);
  %tasks = $adm->get_tasks;

For further information on how to perform specific functions to each of the
tasks, please read on.

=cut

#################################################################
# Object Constructor

=head1 OBJECT CONSTRUCTOR

  $adm = Comas::Admin->new(-db => $db, 
                           -login => $login, 
                           -passwd => $passwd);

This creates a new Comas::Admin object, allowing for administration of some
different areas. Of course, the object will only be created if the provided
login/password are valid. 

The value passwd as $db should be a valid Comas::DB object. Note that for most
administration operations, a regular Comas::DB object may not be enough - 
you might need to supply a Comas::DB object which uses 'comas_adm' (or whatever
administrator user you defined for the database) as the dbuser. If you are using
a regular Comas dbuser, the object will be created, but many methods will fail
to work.

=cut

sub new {
    my ($class, $adm, $sth);
    $class = shift;
    if  (my %adm = valid_hash(@_)) {
       $adm = { %adm };
    } else {
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }

    $adm = { @_ };

    unless (defined $adm->{-db} and defined $adm->{-login} and 
	    defined $adm->{-passwd}) {
	carp 'Invocation error - Must specify -db, -login and -passwd fields';
	return undef;
    }

    unless (scalar keys %$adm == 3) {
	carp 'Invocation error - Unknown parameters received';
	return undef;
    }

    if (ref($adm->{-db}) ne 'Comas::DB') {
	carp "Invocation error - Mandatory '-db' field not of expected type";
	return undef;
    }

    # We were called correctly. Ask the database if the login/password pair is
    # valid.
    unless ($sth = $adm->{-db}->prepare('SELECT ck_admin_passwd(?, ?)') and
	    $sth->execute($adm->{-login}, $adm->{-passwd})) {
	carp 'Database error - Cannot verify admin. Cannot continue.';
	return undef;
    }

    unless ($sth->fetchrow_array) {
	carp "Invalid password for admin login $adm->{-login}";
	return undef;
    }

    bless ($adm, $class);
    return $adm;
}

=head1 INFORMATION LOOKUP

The following methods are available to every user - It is not necessary to
be a valid administrator in order to use them. They will usually be called as
class methods - no existing Comas::Admin object is needed.

  $name = Comas::Admin->get_name(-db=>$db, -login=>$login);
  $name = Comas::Admin->get_name(-db=>$db, -id=>$id);
  $name = $adm->get_name(-login=>$login);
  $name = $adm->get_name(-id=>$id);
  $name = $adm->get_name;

  $login = Comas::Admin->get_login(-db=>$db, -id=>$id);
  $login = $adm->get_login(-id=>$id);
  $login = $adm->get_login;

  $id = Comas::Admin->get_id(-db=>$db, -login=>$login);
  $id = $adm->get_id(-login=>$login);
  $id = $adm->get_id;

  $ok = Comas::Admin->ck_admin_task(-db=>$db, -task=>$task,
                                    ( -id=>$id | -login=>$login ) );
  $ok = $adm->ck_admin_task(-task=>$task, 
                            ( -id=>$id | -login=>$login ) );
  $ok = $adm->ck_admin_task(-task=>$task);

  %tasks = Comas::Admin->get_tasks(-db=>$db);
  %tasks = $adm->get_tasks;

The hash returned by get_tasks has the task descriptions as its keys and the IDs
as its values - It may sound awkward, but it represents better the actual 
usage - the task IDs almost do not need to be used.

The class methods can also be invoked as instance methods (with an existing 
Comas::Admin object). If doing so, it is not necessary to explicitly pass $db.
If any of them is called as an instance method and receives no arguments, it 
will return the requested information on the admin referenced by the calling 
object.

=cut

sub get_name {
    my ($adm, $db, %par, $sth, $ret);
    $adm = shift;

    unless (%par = valid_hash(@_) or scalar @_ == 0) {
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }

    if (ref $adm) {
	$db = $adm->{-db};
	unless (defined $par{-login} or defined $par{-id}) {
	    # We are querying for information on ourselves. Just rephrase it.
	    return $adm->get_name(-login=>$adm->{-login});
	}
    } else {
	# Check that $db is correct
	unless (defined $par{-db} and ref($par{-db})) {
	    carp "Invocation error: Mandatory '-db' field not of expected type";
	    return undef;
	}
	$db = $par{-db};
    }

    if (defined $par{-login} and defined $par{-id}) {
	carp 'Invocation error - Options -id and -login are mutually exclusive';
	return undef;
    } elsif (defined $par{-login}) {
	unless ($sth = $db->prepare('SELECT name FROM admin WHERE login = ?') 
		and $sth->execute($par{-login})) {
	    carp 'Database error while processing query';
	    return undef;
	}
    } elsif (defined $par{-id}) {
	unless ($sth = $db->prepare('SELECT name FROM admin WHERE id = ?') 
		and $sth->execute($par{-id})) {
	    carp 'Database error while processing query';
	    return undef;
	}
    } else {
	carp 'Invocation error - You must specify either -id or -login';
	return undef;
    }

    ($ret) = $sth->fetchrow_array;
    return $ret;
}

sub get_login {
    my ($adm, $login, %par, $sth, $db);
    $adm = shift;

    unless (%par = valid_hash(@_) or scalar(@_) == 0) {
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }

    if (ref $adm and !%par) {
	# We will cheat here: We already know this administartor's login
	return $adm->{-login};
    }

    if (ref $adm) {
	$db = $adm->{-db};
    } else {
	$db = $par{-db};
    }

    unless (defined $par{-id}) {
	carp 'Required -id parameter not supplied';
	return undef;
    }

    unless ($sth = $db->prepare('SELECT login FROM admin WHERE id = ?')
	    and $sth->execute($par{-id}) and ($login)=$sth->fetchrow_array) {
	carp 'Database error while processing query';
	return undef;
    }

    return $login;
}

sub get_id {
    my ($adm, $db, $id, %par, $sth);
    $adm = shift;

    unless (%par = valid_hash(@_) or scalar(@_) == 0) {
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }

    if (ref $adm) {
	$db = $adm->{-db};
	$par{-login} = $adm->{-login} unless defined $par{-login};
    } else {
	$db = $par{-db};
    }

    unless (defined $par{-login}) {
	carp 'Required -login parameter not supplied';
	return undef;
    }

    unless ($sth = $db->prepare('SELECT id FROM admin WHERE login = ?') and
	    $sth->execute($par{-login}) and ($id) = $sth->fetchrow_array) {
	carp 'Database error while processing query';
	return undef;
    }

    return $id;
}

sub get_tasks {
    my ($adm, $db, $sth, %ret);
    $adm = shift;
    %ret = ();
    if (ref $adm) {
	$db = $adm->{-db};
    } else { 
	$db = shift;
	$db = shift if $db eq '-db';
    }

    unless ($sth = $db->prepare('SELECT descr, id FROM task') and
	    $sth->execute) {
	carp 'Database error while processing query';
	return undef;
    }

    while (my @row = $sth->fetchrow_array) {
	$ret{$row[0]} = $row[1];
    }

    return %ret;
}

sub ck_admin_task {
    # Verifies that the administrator has access to a specific task.
    # Returns 1 if the access is granted, 0 if not, undef if the query was not
    # carried out successfully.
    my ($adm, %par, $sth, $db);
    $adm = shift;

    unless (%par = valid_hash(@_)) {
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }

    unless (defined $par{-task}) {
	carp "Required argument 'task' missing";
	return undef;
    }

    if (ref $adm) {
	$db = $adm->{-db};
	unless (defined $par{-login} or defined $par{-id}) {
	    # We are querying for information on ourselves. Just rephrase it.
	    return $adm->ck_admin_task(-login=>$adm->{-login}, 
				       -task=>$par{-task});
	}
    } else {
	# Check that $db is correct
	unless (defined $par{-db} and ref($par{-db})) {
	    carp "Invocation error: Mandatory '-db' field not of expected type";
	    return undef;
	}
	$db = $par{-db};
    }

    if (defined $par{-login} and defined $par{-id}) {
	carp 'Invocation error - Options -id and -login are mutually exclusive';
	return undef;
    } elsif (!defined $par{-login} and !defined $par{-id}) {
	carp 'Invocation error - Either -id or -login must be supplied';
	return undef;
    }

    if (defined $par{-login}) {
	# Get the ID for this login and go ahead.
	# We use this syntax so it will work regardless if we were called as
	# a class or instance method
	$par{-id} = get_id($adm, -db=>$db, -login=>$par{-login});
    }

    unless ($sth = $db->prepare('SELECT count(ta.admin_id) FROM task_admin
            ta, task t WHERE t.id=ta.task_id AND admin_id = ? AND t.descr = ?')
	    and $sth->execute($par{-id}, $par{-task})) {
	# We are doing a what seems an extremely simplistic check here because
	# the 'count' function will always return a single row. No 0E0 checking
	# is needed.
	carp 'Database error while processing query';
	return undef;
    }

    return $sth->fetchrow_array;
}

=head1 OWN PASSWORD MODIFICATION

Every administrator can modify his access password:

  $ok = $adm->set_passwd($newpass);

Returns 1 on success, 0 on failure, undef on error.

=cut

sub set_passwd {
    my ($adm, $newpass, $sth);
    $adm = shift;
    $newpass = shift;

    unless ($sth = $adm->{-db}->prepare('SELECT set_admin_passwd(?, ?)') and
	    $sth->execute($adm->{-login}, $newpass)) {
	carp 'Database error while processing query';
	return undef;
    }

    if ($sth->fetchrow_array) {
	# Success!
	# Update our working copy, return true
	$adm->{-passwd} = $newpass;
	return 1;
    }
    # A sad failure
    return 0;
}

=pod

=head1 ADMINISTRATOR TASKS

Every administrator will have its different access denoted by one or more of
the following tasks:

=head1 ADMINISTRATORS MANAGEMENT (admin_mgr)

This is the most important category, and -for practical effect- an administrator
having this privilege level will have complete access to any function in this
module. People in this group can add, delete or modify  administrators' records,
tasks (defined privilege levels), and can grant/revoke different task access to
the different administrators.

The methods available to administrators having this privilege level are:

=over 4

=item * Querying for the list of administrators

  @adms = $adm->admin_list;

Returns the list of the existing registered administrators. The list is an
array of arrays, with each element being an administrator, and containing the 
administrator's ID as the first element, the name as the second and the login
as the third element. Passwords are B<not> retrievable.

=item * Creating a new administrator

  $id = $adm->create_admin($login, $name, $passwd);
  $id = $adm->create_admin($login, $name, $passwd, $tasks);

Returns the newly created administrator's ID on success, undef on failure. 
Login, name and password are required. Tasks should be an array containing
the names (descriptions) of the tasks that the new administrator will be able
to perform.

=item * Removing an administrator

  $ok = $adm->remove_adm($id);

=item * Modifying an administrator's data

  $ok = $adm->set_adm_name($id, $newname);
  $ok = $adm->set_adm_login($id, $login);
  $ok = $adm->set_adm_passwd($id, $newpasswd);

=item * Querying for an administrator's tasks

  @tasks = $adm->query_admin_tasks($adm_id)

The values in the returned array are the descriptions of the tasks. Refer to 
the note on the documentation for C<get_tasks> as well.

=item * Granting/revoking administration tasks to administrators

  $ok = $adm->grant_task_adm($id, $task);
  $ok = $adm->revoke_task_adm($id, $task);

=item * Managing administration tasks

Note that, except for the following methods, we always refer to tasks by their
description, not by their IDs. 

  $id = $adm->create_task($task);
  $ok = $adm->delete_task($task);

=back

You might add arbitrary tasks used by your applications. We suggest, however,
B<not> to remove the following predefined tasks, as you will likely end up with
a non-fully-functional system:

=over 4

=item * academic_committee: Setting status and scheduling of proposals 
(See L<Comas::Schedule>)

=item * admin_mgr: Administrators management

=item * monetary_adm: Money-related issues

=item * person_cat_adm: Person-related catalog administration

=item * person_adm: Person's information administration

=item * proposal_cat_adm: Proposal-related catalog administration

=item * schedule_adm: Rooms and schedule administration

=item * sys_conf: System configuration

=back

The admin_mgr task is special: It is the only task that can not be deleted
or left with no members.

The code for admin_mgr is implemented in the L<Comas::Admin::admin_mgr>
module.

=head1 PERSON-RELATED CATALOG ADMINISTRATION (person_cat_adm)

Administrators in this category can create, modify or delete entries in
the person-related database catalogs (studies, person_type, state, country,
pers_status). This is done through the following methods:

=head2 Querying

  @catalogs = $adm->get_person_catalogs;

@catalogs will contain the names of the catalogs which are considered
person-related (and, of course, managed via this module)

=head2 Creation

  $id = $adm->create_studies($descr);
  $id = $adm->create_person_type($descr);
  $id = $adm->create_state($descr);
  $id = $adm->create_country($descr);
  $id = $adm->create_pers_status($descr);

All of them take as their only parameter the description (i.e. name) of the
record to be created, and return its newly assigned ID.

=head2 Modification

  $ok = $adm->set_studies($id, $descr);
  $ok = $adm->set_person_type($id, $descr);
  $ok = $adm->set_state($id, $descr);
  $ok = $adm->set_country($id, $descr);
  $ok = $adm->set_pers_status($id, $descr);

=head2 Deletion

  $ok = $adm->delete_studies($id);
  $ok = $adm->delete_person_type($id);
  $ok = $adm->delete_state($id);
  $ok = $adm->delete_country($id);
  $ok = $adm->delete_pers_status($id);

The code for person_cat_adm is implemented in the 
L<Comas::Admin::person_cat_adm> module.

=head1 PERSON'S INFORMATION ADMINISTRATION (person_adm)

Administrators in this category can get listings for the registered persons
(including filtering by certain criteria), and modify the personal information
for any of them. 

=head2 Lookup

  @ids = $adm->search(-attr1=>$value1,
                      -attr2=>$value2,
                      (...));

Returns the list of persons who satisfy the given criteria, or an empty list
if no person satisfies it. If no attribute-value pairs are given, the full user
list is returned.

  %data = $adm->get_person_data($id);

The whole data describing a person is returned.

=head2 Modification

    $ok = $adm->set_person_firstname($id, $firstname);
    $ok = $adm->set_person_famname($id, $famname);
    $ok = $adm->set_person_nickname($id, $nick);
    $ok = $adm->set_person_passwd($id, $passwd);
    $ok = $adm->set_person_org($id, $org);
    $ok = $adm->set_person_dept($id, $dept);
    $ok = $adm->set_person_title($id, $title);
    $ok = $adm->set_person_studies_id($id, $studies);
    $ok = $adm->set_person_email($id, $email);
    $ok = $adm->set_person_person_type_id($id, $adm_type);
    $ok = $adm->set_person_postal_addr($id, $addr);
    $ok = $adm->set_person_zip($id, $zip);
    $ok = $adm->set_person_city($id, $city);
    $ok = $adm->set_person_state_id($id, $state);
    $ok = $adm->set_person_country_id($id, $country);
    $ok = $adm->set_person_phone($id, $phone);
    $ok = $adm->set_person_fax($id, $fax);
    $ok = $adm->set_person_birth($id, $birth);
    $ok = $adm->set_person_resume($id, $resume);
    $ok = $adm->set_person_pers_status_id($id, $status);

If you want to change more than one attributes for a given person, you can use:

    $ok = $adm->set_person_data(-id=>$id,
                            -attr1=>$value1,
                            -attr2=>$value2, 
                            (...));

In order to permanently delete a person, use:

    $ok = $adm->delete_person(-id=>$id);

Note that once a person is removed, all of his personal data, his 
participation in any proposal and the proposals itself if the person is the
only author is gone for good - No recovery/undeletion is possible.

The code for person_adm is implemented in the L<Comas::Admin::person_adm>
module.

=head1 PROPOSAL-RELATED CATALOG ADMINISTRATION (proposal_cat_adm)

Administrators in this category can create, modify or delete entries in the
proposal-related database catalog (track, prop_status, prop_type). This is 
done through the following methods:

=head2 Creation

  $id = $adm->create_track($descr);
  $id = $adm->create_prop_status($descr);
  $id = $adm->create_prop_type($descr);

=head2 Modification

  $ok = $adm->set_track($id, $descr);
  $ok = $adm->set_prop_status($id, $descr);
  $ok = $adm->set_prop_type($id, $descr);

=head2 Deletion

  $ok = $adm->delete_track($id);
  $ok = $adm->delete_prop_status($id)
  $ok = $adm->delete_prop_type($id);

The code for proposal_cat_adm is implemented in the 
L<Comas::Admin::proposal_cat_adm> module.

=head1 ROOMS AND SCHEDULE ADMINISTRATION (schedule_adm)

Administrators in this category can create, modify or delete rooms and
timeslots, and create the relationships between timeslots and proposal types
(timeslot_prop_type):

=head2 Rooms

  $id = $adm->create_room(-descr=>$descr);
  $ok = $adm->set_room(-id=>$id, -descr=>$descr);
  $ok = $adm->delete_room(-id=>$id);

Note that whenever a room is deleted, all timeslots belonging to that room will
be deleted as well, and any proposals scheduled for that room will be 
unscheduled.

=head2 Rooms, proposal type and max people relations

A room can have a limit number of attendees per proposal type. In order to
specify this limit we can use:

$ok = $adm->set_room_max_people(-id=>$id, -prop_type=>$prop_type,
                                -max_people=>$max_people);
$ok = $adm->unset_room_max_people(-id=>$id, -prop_type=>$prop_type);

If the realtion is not set or max people is equal to 0, there will not be limit
in that room for the specified proposal type.

In both cases, if no action needs to be taken (i.e., if the relation is not
set when trying to unset it or if it is alreaedy set when setting it), the
return value will be 1 (success).

=head2 Timeslots

  $id = $adm->create_timeslot(-start_hr=>$hr, -day=>$day,
			      -room_id=>$room);
  $ok = $adm->set_timeslot(-id=>$id, [ -start_hr=>$hr ], 
			   [ -day => $day ], [ -room_id=>$room ])
  $ok = $adm->delete_timeslot(-id=>$id);

Note that whenever a timeslot is deleted, all the timeslot_prop_type entries
associated to it are deleted, and any proposals scheduled for it will be 
unscheduled.

=head2 Timeslot and proposal type relations

A timeslot can host proposals of different types. In order to specify which
kinds of proposals we will be able to use for a given timeslot, we can use:

  $ok = $adm->set_timeslot_prop_type(-timeslot=>$t_id, -type=>$p_id)
  $ok = $adm->unset_timeslot_prop_type(-timeslot=>$t_id, -type=>$p_id)

In both cases, if no action needs to be taken (i.e., if the relation is not
set when trying to unset it or if it is alreaedy set when setting it), the
return value will be 1 (success).

For further operations that can be carried out using timeslots, check the
L<Comas::Schedule> module.

The code for schedule_adm is implementented in the L<Comas::Admin::schedule_adm>
module.

=head1 MONEY-RELATED ISSUES (monetary_adm)

Administrators in this category can create, modify or delete exp_status
(expense status), payment_type, time_discount and price entries using the 
following methods:

=head2 Creation

  $id = $adm->create_exp_status($descr);
  $id = $adm->create_payment_type($descr);
  $id = $adm->create_time_discount($max_date, $discount);
  $ok = $adm->create_price($person_type_id, $prop_type_id, $amount);

=head2 Modification

  $ok = $adm->set_exp_status($id, $descr);
  $ok = $adm->set_payment_type($id, $descr);
  $ok = $adm->set_time_discount(-id=>$id, [ -max_date=>$date ],
				[ -discount=>$discount ]);
  $ok = $adm->set_price($person_type_id, $prop_type_id, $amount);

=head2 Deletion

  $ok = $adm->delete_exp_status($id);
  $ok = $adm->delete_payment_type($id);
  $ok = $adm->delete_time_discount($id);
  $ok = $adm->delete_price($person_type_id, $prop_type_id);

Note that, being the C<price> table a relationship and not a catalog, you must
pass the values of the combination it represents (the IDs of both the 
C<person_type> and C<prop_type> tables) in order to modify or delete it. 

The code for monetary_adm is implemented in the L<Comas::Admin::monetary_adm>
module.

=head1 SETTING STATUS AND SCHEDULING OF PROPOSALS (academic_committee)

Administrators for academic_committee can modify the status of proposals, and 
assign/modify/unassign them to either specific timeslots in the schedule or
ask the system to assign them at the timeslot it sees more fit. The following
methods are available:

  $ok = $adm->set_status($prop_id, $prop_status_id);

  $ok = $adm->set_comments($prop_id, $comments);

  $ok = $adm->set_track_id($prop_id, $track_id);

  $ok = $adm->set_prop_type_id($prop_id, $prop_type_id);

  $ok = $adm->set_abstract($prop_id, $abstract);

  $tslot_id = $adm->schedule(-prop=>$prop_id, -timeslot=>$timeslot_id);

  $tslot_id = $adm->schedule(-prop=>$prop_id, [ -room=>$room_id ],
                             [ -day => $day ], [ -start_hr=>$start_hr ]
                             [ -timeslot => $timeslot_id ]);

  $ok = $adm->unschedule($prop_id);

=cut

#################################################################
# Private methods - not for human consumption

sub _create_record {
    # Creates a record in a given catalog. 
    # Receives three parameters:  A Comas::DB object, the name of the catalog
    # and the description for the new record.
    # Returns the new item's ID if successful, undef if failed.
    # Does not validate that the user has the right to perform this operation,
    # remember this is a private function and should not be called but from a
    # Comas::Admin method.
    my ($db, $cat, $descr, $sth);
    $db = shift;
    $cat = shift;
    $descr = shift;

    # If the requested value already exists in the table, just return 
    # successfully
    unless ($sth = $db->prepare("SELECT id FROM $cat WHERE descr = ?") and
	    $sth->execute($descr)) {
	carp "Could not check for preexistence in $cat";
	return undef;
    }
    if (my ($id) = $sth->fetchrow_array) {
	return $id;
    }

    $db->begin_work;
    unless ($sth = $db->prepare("INSERT INTO $cat (descr) VALUES (?)") and
	    $sth->execute($descr)) {
	carp "Could not insert value into $cat";
	$db->rollback;
	return undef;
    }

    unless ($sth = $db->prepare("SELECT currval('${cat}_id_seq')") and
	    $sth->execute) {
	carp "Could not verify new record's ID - Not inserting it.";
	$db->rollback;
	return undef;
    }
    $db->commit;
    return $sth->fetchrow_array;
}

sub _set_record {
    # Sets the specified value for a record in a given catalog. 
    # Receives four parameters:  A Comas::DB object, the name of the catalog,
    # the ID for the record to be modified and the new description for the
    # record.
    # Returns 1 if successful, undef if failed.
    # Does not validate that the user has the right to perform this operation,
    # remember this is a private function and should not be called but from a
    # Comas::Admin method.
    my ($db, $cat, $id, $descr, $sth);
    $db = shift;
    $cat = shift;
    $id = shift;
    $descr = shift;

    unless ($sth = $db->prepare("UPDATE $cat SET descr = ? WHERE id = ?") and
	    $sth->execute($descr, $id)) {
	carp "Could not change requested value in $cat";
	return undef;
    }
    return 1;
}

sub _delete_record {
    # Deletes a record from a given catalog. 
    # Receives three parameters:  A Comas::DB object, the name of the catalog
    # and the ID for the record to be deleted.
    # Returns 1 if successful, undef if failed.
    # Does not validate that the user has the right to perform this operation,
    # remember this is a private function and should not be called but from a
    # Comas::Admin method.
    my ($db, $cat, $id, $sth);
    $db = shift;
    $cat = shift;
    $id = shift;

    unless ($sth = $db->prepare("DELETE FROM $cat WHERE id = ?") and
	    $sth->execute($id)) {
	carp "Could not remove value from $cat";
	return undef;
    }
    return 1;
}

=head1 REQUIRES

L<Comas::DB|Comas::DB>

=head1 SEE ALSO

Comas' databse documentation, http://wiki.consol.org.mx/comas_wiki/

=head1 AUTHOR

Gunnar Wolf, gwolf@gwolf.cx

Manuel Rabade, mig@mig-29.net

Comas has been developed for CONSOL, Congreso Nacional de Software Libre,
http://www.consol.org.mx/

=head1 COPYRIGHT

Copyright 2003 Gunnar Wolf and Manuel Rabade

This library is free software, you can redistribute it and/or modify it
under the terms of the GPL version 2 or later.

=cut

1;

# $Log: Admin.pm,v $
# Revision 1.24  2004/10/05 21:53:31  gwolf
# Adds the functionality to query on person catalogs (through
# get_person_catalogs). Ugly, hard-coded... :-/ But needed. And the
# future looks better.
#
# Revision 1.23  2004/07/28 15:40:54  gwolf
# Added system configuration functionality
#
# Revision 1.22  2004/07/24 18:18:06  gwolf
# Documentation
#
# Revision 1.21  2004/05/10 15:47:12  gwolf
# When reviewing proposals, the committee can now also modify the
# abstracts
#
# Revision 1.20  2004/04/30 19:43:23  gwolf
# Implemented person deletion in Comas::Admin::person_adm,
# Comas::HTML::committee::person_adm. Modified other files for the
# inclusion of this functionality.
#
# Revision 1.19  2004/04/27 21:48:13  gwolf
# Both files: Little documentation changes
#
# Revision 1.18  2004/04/23 22:24:07  gwolf
# Fixed a little bug in order to be able to call get_login as a class
# method
# Documented the new admin_list method (for admin_mgr)
#
# Revision 1.17  2004/03/29 07:22:33  gwolf
# Added a new administration module, for managing information regarding persons. Basically reimplemented what is already available in Person, but via the Admin interface and with the Admin checks.
#
# Revision 1.16  2004/02/04 17:36:29  mig
# - Agrego documentacion para set_room_max_people y unset_room_max_people
#
# Revision 1.15  2004/01/05 19:36:32  gwolf
# - Schedule.pm, Schedule/Room.pm: Por fin ya agendo sobre cualquier criterio!
#   (sólo falta hacer que evite sobrecargar de ponencais con el mismo track un
#   mismo espacio)
# - Admin/academic_committee.pm: Agrego unas funcioncillas que pidió Mig
# - Admin.pm: Quito todas las referencias a db_param, que ya no usaremos
#
# Revision 1.14  2003/12/20 04:14:51  mig
# - Agrego tags Id y Log que expanda el CVS
#
