# $Id: Person.pm,v 1.26 2004/06/25 03:15:47 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::Person
# Handles the interaction with a person for Comas
######################################
# Depends on:
#
# Comas::Common - Common functions for various Comas modules
package Comas::Person;

use strict;
use warnings;
use Carp;
use Comas::Common qw(valid_hash);


=head1 NAME

Comas::Person - Handles the interaction with a person for Comas

=head1 SYNOPSIS

=head2 OBJECT CONSTRUCTOR

    $person = Comas::Person->new(-db=>$db,
                                 -id=>$number);

or

    $person = Comas::Person->new(-db=>$db,
                                 -login=>$login,
                                 -passwd=>$passwd);

or

    $person = Comas::Person->new(-db=>$db,
                                 -firstname=>$first,
                                 -famname=>$fam,
                                 -nickname=>$nick,
                                 (...));

The constructor must receive a Comas::DB object in the C<-db> attribute.

The presence of the C<-id> attribute in the constructor implies that no other
descriptor attribute will be supplied - if C<new> is called with both kinds of
attributes, it will fail.

If C<-login> and C<-password> are specified, the object will only be successfully
created if the person is already registered and the password is valid for this
login. No search will take place, even if the data received was otherwise valid.

The attributes accepted by the constructor are (check Comas' database
documentation for further information) C<-firstname>, C<-famname>, C<-login>,
C<-nickname>, C<-passwd>, C<-org>, C<-dept>, C<-title>, C<-studies_id>, 
C<-email>, C<-person_type_id>, C<-postal_addr>, C<-zip>, C<-city>, C<-state_id>,
C<-country_id>, C<-phone>, C<-fax>, C<-birth>, C<-resume>, C<-pers_status_id>.

=head2 LOOKUP FUNCTIONS

    @ids = Comas::Person->search(-db=>$db,
                                 -attr1=>$value1,
                                 -attr2=>$value2, 
                                 (...));
    @ids = $p->search(-attr1=>$value1,
                      -attr2=>$value2,
                      (...));

Will return in C<@ids> the IDs of the persons who match the given criteria. Of
course, if no person is found matching the criteria, an empty list will be
returned. All the attributes valid for the constructor are valid.

You can use search either as a regular function (A.K.A. a class method) or as a
method (A.K.A. an instance method). Note that C<-db>, a Comas::DB object, is 
mandatory if you call it as a function, not if you call it as a method. 

Note that this will do a B<very> simple search - The matches must be exact
(i.e. there is no way of telling C<search> to search for regular expressions or
even comparisons (less than, greater than, etc). If you need that functionality,
you should implement it via Comas::DB.

    @prop_ids = $p->get_props

Will return the IDs of the proposals in which this person is registered as an
author (irrespective of the place he occupies in it). An empty list will be
returned if the person is not registered as an author.

    $ok = $p->can_submit_proposals;

Will return a true value if this person can still submit new proposals (this
means, if he is not taking part in the maximum allowed number of proposals per
author)

    $id = $p->ck_passwd(-login=>$login, -passwd=>$passwd);

    $id = Comas::Person->ck_passwd(-db=>$db,
                                   -login=>$login,
                                   -passwd=>$passwd);

If the login/password pair is correct, this method will return the corresponding
ID for that person. If it is not correct, it will return undef.

=head2 ATTRIBUTE ACCESSORS AND MUTATORS

In order to access the person's attributes, you can use:

    $id = $person->get_id;
    $firstname = $person->get_firstname;
    $famname = $person->get_famname;
    $login = $person->get_login;
    $nick = $person->get_nickname;
    $org = $person->get_org;
    $dept = $person->get_dept;
    $title = $person->get_title;
    $studies = $person->get_studies_id;
    $email = $person->get_email;
    $person_type = $person->get_person_type_id;
    $addr = $person->get_postal_addr;
    $zip = $person->get_zip;
    $city = $person->get_city;
    $state = $person->get_state_id;
    $country = $person->get_country_id;
    $phone = $person->get_phone;
    $fax = $person->get_fax;
    $birth = $person->get_birth;
    $resume = $person->get_resume;
    $status = $person->get_pers_status_id;

If you want to get all the attributes for a given person in a single hash (and
in a single operation), you can use:

    %data = $person->get_data;

And if you want to modify the current value for any of the attributes:

    $ok = $person->set_firstname($firstname);
    $ok = $person->set_famname($famname);
    $ok = $person->set_nickname($nick);
    $ok = $person->set_passwd($passwd);
    $ok = $person->set_org($org);
    $ok = $person->set_dept($dept);
    $ok = $person->set_title($title);
    $ok = $person->set_studies_id($studies);
    $ok = $person->set_email($email);
    $ok = $person->set_person_type_id($person_type);
    $ok = $person->set_postal_addr($addr);
    $ok = $person->set_zip($zip);
    $ok = $person->set_city($city);
    $ok = $person->set_state_id($state);
    $ok = $person->set_country_id($country);
    $ok = $person->set_phone($phone);
    $ok = $person->set_fax($fax);
    $ok = $person->set_birth($birth);
    $ok = $person->set_resume($resume);
    $ok = $person->set_pers_status_id($status);

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

    $ok = $person->set_data(-attr1=>$value1,
                            -attr2=>$value2, 
                            (...));

Note that no C<$person-E<gt>get_passwd> or C<$person-E<gt>set_login> methods are 
provided or you can't use the C<-password> key in C<$person-E<gt>set_data>.

The password cannot be retrieved. If necessary, a new one should be 
created. The login cannot be changed.

=head2 CLASS METHODS

If you want to see the last error of the Comas::Person module, you could use:

    $err = Comas::Person->lastError;

And it will return a numeric code of the last error. The list of error codes is:

=over

=item General Error Codes:

 0 - Unknow Error
 1 - Wrong number of parameters
 2 - Mandatory '-db' field not specified or wrong
 3 - Invalid Keys recived
 4 - Missing or more information
 5 - Cannot search for an empty string
 6 - Invocation Error
 7 - Cannot insert empty value

=item Database Error Codes:

 101 - Could not prepare query
 102 - Could not execute query
 103 - Could not perform query

=item Person.pm Error Codes:

 200 - Internal Error
 201 - '-id' given, no other attributes accepted
 202 - Could not find requested person
 203 - Invalid login/password pair
 204 - More than one person matched
 205 - Duplicated Person
 206 - Could not query for the new persons' id
 207 - Could not check if the password is valid

=back

=head1 REQUIRES

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

L<Comas::Common|Comas::Common>

=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

#################################################################
# Class Metods

my $lastError;

sub lastError {
    return $lastError;
}

sub setLastError {
    $lastError = shift;
}
    

#################################################################
# Object constructor

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

    if (ref($p->{-db}) ne 'Comas::DB') {
        setLastError(2);
	carp "Invocation error - mandatory '-db' field not specified or wrong";
	return undef;
    }

    if (defined $p->{-id}) {
	# An ID was given - Check nothing else was received and retreive the 
	# person's data.
	my ($sth);
	if (scalar keys %$p > 2) {
            setLastError(201);
	    carp "Invocation error - '-id' given, no other attributes accepted";
	    return undef;
	}
	unless ($sth = $p->{-db}->prepare('SELECT firstname, famname, login,
            nickname, passwd, org, dept, title, studies_id, email, 
            person_type_id, postal_addr, zip, city, state_id, phone, fax, birth,
            resume, pers_status_id FROM person WHERE id = ?')) {
            setLastError(101);
            carp 'Could not prepare person query: ', $p->{-db}->lastMsg;
	    return undef;
	}
	if ($sth->execute($p->{-id}) eq '0E0') {
            setLastError(202);
	    carp 'Could not find requested person';
	    return undef;
	}

        unless($sth->execute($p->{-id})) {
            setLastError(102);
            carp 'Could not execute person query: ', $p->{-db}->lastMsg;
            return undef;
        }

	# Ok, everything went fine - Now bring the information from the DB
	# into $p and return it
	my $r = $sth->fetchrow_hashref;

	map {$p->{"-$_"} = $r->{$_}} keys %$r;

    } elsif (defined $p->{-login} and defined $p->{-passwd} and
             scalar keys %$p == 3) {
	if (my $id=Comas::Person->ck_passwd(-db=>$p->{-db}, -login=>$p->{-login},
					    -passwd=>$p->{-passwd})) {
	    # We got a valid person, we got his ID - Create the object 
	    # (overwriting our present $p)
	    $p = Comas::Person->new(-db=>$p->{-db}, -id=>$id) or return undef;
	} else {
            setLastError(203);
	    carp 'Invalid login/password pair - Cannot continue';
	    return undef;
	}
    } elsif (my @persons = Comas::Person->search(%$p)) {
	# There is at least one person matching the set of data we are looking
	# for. If it is only one person, go ahead and create the object. If we 
	# get more than one person, complain and return undef.
	if (scalar @persons > 1) {
            setLastError(204);
	    carp 'More than one person matched - Cannot continue.';
	    return undef;
	} elsif (!defined $persons[0]) {
            setLastError(200);
	    carp 'This seems like an impossible situation - Someone found ',
	    'but it is an empty set? Bailing out...';
	    return undef;
	}
	$p = Comas::Person->new(-db=>$p->{-db}, -id=>$persons[0]) or 
	    return undef;

    } else {
	# We received the list of values to insert and found no matching value 
	# in the database - Check the values are valid, create the new record,
	# retreive the ID and create the new Comas::Person object
	my ($sql, @keys, @values, $sth, $id, $ck);

	$ck = _ck_valid_keys($p);
	if (not $ck) {
            setLastError(3);
	    carp 'Invalid keys received for person creation';
	    return undef;
	}
	if (not $ck & 2) {
            setLastError(4);
	    carp 'Cannot create person - missing or more information';
	    return undef;
	}

	$sql = 'INSERT INTO person (';

	# We store the ordered attributes in the arrays @keys and @values
	foreach my $k (keys %$p) {
	    next if $k eq '-db';
	    push(@values, $p->{$k});

	    $k =~ s/^-//;
	    push(@keys, $k);
	}

	$sql .= join(', ', @keys);
	$sql .= ') VALUES (' . join(', ', map {'?'} @keys) . ')';

	# We now have a nice SQL insert statement with placeholders
	# and everything. Now do the dirty job...

	$p->{-db}->begin_work;
	unless ($sth = $p->{-db}->prepare($sql)) {
            setLastError(101);
	    carp 'Could not prepare person creation query: ', 
	    $p->{-db}->lastMsg;
	    $p->{-db}->rollback;
	    return undef;
	}

	unless ($sth->execute(@values)) {
            if (index($p->{-db}->lastMsg, 'DUPLICATED_PERSON') != -1) {
                setLastError (205);
            } else {
                setLastError (102);
            }
            carp "Could not execute person creation query: ",
            $p->{-db}->lastMsg;
	    $p->{-db}->rollback;
	    return undef;
	}

	# The entry was created successfully! Now retrieve the new person ID
	# and return the new object.
	unless ($sth = $p->{-db}->prepare("SELECT currval('person_id_seq')") and
		$sth->execute and ($id) = $sth->fetchrow_array) {
            setLastError(206);
	    carp "Could not query for the new persons' ID: ", 
	    $p->{-db}->lastMsg;
	    $p->{-db}->rollback;
	    return undef;
	}
	$p->{-db}->commit;
	$p = Comas::Person->new(-db=>$p->{-db}, -id=>$id) or return undef;
    }
    
    bless ($p, $class);
    return $p;
}

#################################################################
# Lookup functions

sub search {
    # Returns the list of persons which conform to a given criteria
    # Receives a Comas::DB object as its first parameter, and a list of
    # key-value pairs for the following parameters.
    my ($p, $db, $sql, $sth, @attr, %vals);
    $p = shift;
    @attr = ();
    
    return undef unless @_;

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

    # Were we called as an instance method and not as a class method? Ok, go
    # ahead
    $db = $p->{-db} if ref $p;

    while (@_) {
	my $key = shift;
	my $value = shift;
        $value =~ s/^\ +//;

        if ($value =~ /^\s*$/) {
            setLastError(5);
            carp 'Cannot search for an empty string';
            return undef;
        }

	if ($key eq '-db') {
	    $db = $value;
	    next;
	}

	push(@attr, $key);
	$vals{$key} = $value;
    }

    unless (ref($db)) {
        setLastError(2);
	carp 'No valid database handler received:';
	return undef;
    }

    # If we got no attributes, we are searching for a list of all persons
    if (scalar @attr) {
	my (@attr_tmp, %reg_srch);
	# We are performing a search on specific attributes
	unless (_ck_valid_keys(\%vals)) {
            setLastError(3);
	    carp 'Invalid keys received';
	    return undef;
	}

        @attr_tmp = @attr;
	%reg_srch = (firstname=>1, famname=>1, email=>1, nickname=>1);
	
	$sql = 'SELECT id FROM person WHERE ' . 
	    join (' AND ', map { s/^-//; $reg_srch{$_} ? "$_ ~* ?" : "$_ = ?"} 
		  @attr_tmp);

    }  else {
	# We just want the list of all persons
	$sql = 'SELECT id FROM person';
    }
    unless ($sth = $db->prepare($sql)) {
        setLastError(101);
	carp 'Could not prepare query: ', $db->lastMsg;
	return undef;
    }
   
    unless ($sth->execute(map {$vals{$_}} @attr)) {
        setLastError(102);
	carp 'Could not execute query: ', $db->lastMsg;
	return undef;
    }
    
    return map {$_->[0]} @{$sth->fetchall_arrayref};
}

sub get_props {
    my ($p, $sth);
    $p = shift;

    unless ($sth = $p->{-db}->prepare('SELECT proposal_id FROM authors
            WHERE person_id = ?') and $sth->execute($p->{-id})) {
	carp 'Could not perform query';
        setLastError(103);
	return undef;
    }

    return map {$_->[0]} @{$sth->fetchall_arrayref};
}

sub can_submit_proposals {
    my ($p, $max_props, $sent_props);
    $p = shift;

    $max_props = $p->{-db}->get_conf('max_proposals_x_person');
    $sent_props = scalar $p->get_props;

    # If $max_props is 0, we have no limit set - The person can submit.
    return 1 unless $max_props;

    return ($sent_props < $max_props) ? 1 : 0;
}

sub ck_passwd {
    my ($p, %par, $sth, @id);
    $p = shift;
    %par = valid_hash(@_);

    if (ref $p) {
	# Make it work regardless if it was called as a class or an instance
	# method
	$par{-db} = $p->{-db};
    }

    if (scalar(keys %par) != 3 or !defined $par{-db} or !defined $par{-login}
	or !defined $par{-passwd}) {
        setLastError(6);
	carp 'Invocation error';
	return undef;
    }

    unless ($sth = $par{-db}->prepare('SELECT ck_person_passwd(?, ?)') and
	    $sth->execute($par{-login}, $par{-passwd})) {
        setLastError(207);
	carp 'Could not check if the password is valid - Aborting';
	return undef;
    }

    # If the password is not valid, return undef
    return undef unless $sth->fetchrow_array;

    # The DB guarantees us that the login is unique - but it will do no harm to
    # be wary about receiving too many or too few :)
    @id = Comas::Person->search(-db=>$par{-db}, -login=>$par{-login});
    if (scalar @id != 1) {
        setLastError(200);
	carp 'Too few/many IDs found - Possible consistency error. Aborting.';
	return undef;
    }

    return $id[0];
}

#################################################################
# Attribute accessors

# Not too much to say about boring accessors...
sub get_id { my $p=shift; return $p->{-id} }
sub get_firstname { my $p=shift; return $p->_get_attr('firstname'); }
sub get_famname { my $p=shift; return $p->_get_attr('famname'); }
sub get_login { my $p=shift; return $p->_get_attr('login'); }
sub get_nickname { my $p=shift; return $p->_get_attr('nickname'); }
sub get_org { my $p=shift; return $p->_get_attr('org'); }
sub get_dept { my $p=shift; return $p->_get_attr('dept'); }
sub get_title { my $p=shift; return $p->_get_attr('title'); }
sub get_studies_id { my $p=shift; return $p->_get_attr('studies_id'); }
sub get_email { my $p=shift; return $p->_get_attr('email'); }
sub get_person_type_id { my $p=shift; return $p->_get_attr('person_type_id'); }
sub get_postal_addr { my $p=shift; return $p->_get_attr('postal_addr'); }
sub get_zip { my $p=shift; return $p->_get_attr('zip'); }
sub get_city { my $p=shift; return $p->_get_attr('city'); }
sub get_state_id { my $p=shift; return $p->_get_attr('state_id'); }
sub get_country_id { my $p=shift; return $p->_get_attr('country_id'); }
sub get_phone { my $p=shift; return $p->_get_attr('phone'); }
sub get_fax { my $p=shift; return $p->_get_attr('fax'); }
sub get_birth { my $p=shift; return $p->_get_attr('birth'); }
sub get_resume { my $p=shift; return $p->_get_attr('resume'); }
sub get_pers_status_id { my $p=shift; return $p->_get_attr('pers_status_id'); }

sub get_data {
    # Gets all the data available through the accessors and returns it in a nice
    # hash. Receives no parameters.
    my ($p, $sth, %ret);
    $p = shift;

    $sth = $p->{-db}->prepare('SELECT firstname, famname, login, nickname, org,
    dept, title, studies_id, email, person_type_id, postal_addr, zip, city, 
    state_id, country_id, phone, fax, birth, resume, pers_status_id FROM person
    WHERE id = ?') or return undef;
    $sth->execute($p->{-id}) or return undef;

    # Retreive the values and prepend the keys with our beloved dash before
    # returning them
    %ret = %{$sth->fetchrow_hashref}; 
    return map { my $tmp="-$_"; $tmp => $ret{$_} } keys %ret;
}

#################################################################
# Attribute mutators

# Not too much to say about boring mutators...
sub set_firstname { my $p=shift; return $p->_set_attr('firstname',shift); }
sub set_famname { my $p=shift; return $p->_set_attr('famname',shift); }
sub set_nickname { my $p=shift; return $p->_set_attr('nickname',shift); }
sub set_passwd { my $p=shift; return $p->_set_attr('passwd',shift); }
sub set_org { my $p=shift; return $p->_set_attr('org',shift); }
sub set_dept { my $p=shift; return $p->_set_attr('dept',shift); }
sub set_title { my $p=shift; return $p->_set_attr('title',shift); }
sub set_studies_id { my $p=shift; return $p->_set_attr('studies_id',shift); }
sub set_email { my $p=shift; return $p->_set_attr('email',shift); }
sub set_person_type_id { my $p=shift; return $p->_set_attr('person_type_id',shift); }
sub set_postal_addr { my $p=shift; return $p->_set_attr('postal_addr',shift); }
sub set_zip { my $p=shift; return $p->_set_attr('zip',shift); }
sub set_city { my $p=shift; return $p->_set_attr('city',shift); }
sub set_state_id { my $p=shift; return $p->_set_attr('state_id',shift); }
sub set_country_id { my $p=shift; return $p->_set_attr('country_id',shift); }
sub set_phone { my $p=shift; return $p->_set_attr('phone',shift); }
sub set_fax { my $p=shift; return $p->_set_attr('fax',shift); }
sub set_birth { my $p=shift; return $p->_set_attr('birth',shift); }
sub set_resume { my $p=shift; return $p->_set_attr('resume',shift); }
sub set_pers_status_id { my $p=shift; return $p->_set_attr('pers_status_id',shift); }

sub set_data {
    my $p = shift;
    my (%update, $update, $sql, @keys, @values, $sth, $ck);
    
    if (%update = valid_hash(@_)) {
	$update = { %update };
    } else {
        setLastError(6);
	carp 'Invocation error - Wrong number of parameters';
	return undef;
    }
    
    $ck = _ck_valid_keys($update);
    if (not $ck) {
        setLastError(3);
        carp 'Invalid keys received for person update';
        return undef;
    }
    if (not $ck & 4) {
        setLastError(4);
        carp 'Cannot update person - missing or more information';
        return undef;
    }
    
    $sql = 'UPDATE person SET ';
    
    # We store the ordered attributes in the arrays @keys and @values
    foreach my $k (keys %$update) {
        if ($update->{$k} eq '') {
            push(@values, undef);
        } else {
            push(@values, $update->{$k});
        }
        $k =~ s/^-//;
        push(@keys, $k);
    }
    
    $sql .= join(', ', map { $_ . ' = ? '} @keys);
    $sql .= ' WHERE id = ' . $p->{-id};

    # We now have a nice SQL update statement with placeholders
    # and everything. Now do the dirty job...
    
    $p->{-db}->begin_work;
    unless ($sth = $p->{-db}->prepare($sql)) {
        setLastError(101);
        carp 'Could not prepare person update query: ', 
        $p->{-db}->lastMsg;
        $p->{-db}->rollback;
        return undef;
    }
    
    unless ($sth->execute(@values)) {
        carp "Could not execute person update query: ",
        setLastError(102);
        $p->{-db}->lastMsg;
        $p->{-db}->rollback;
         return undef;
    }
    $p->{-db}->commit;
}

###########################################################################
# Internal functions, not for human consumption

sub _get_attr {
    # Called by attribute accessors. Gets as its only parameter the 
    # name of the attribute to fetch from the database.
    my ($p, $attr, $sth, $res);
    $p = shift;
    $attr = shift;
    $attr =~ s/^-//;

    $sth = $p->{-db}->prepare("SELECT $attr FROM person WHERE id = ?");
    $sth->execute($p->{-id});

    return $sth->fetchrow_array;
}

sub _set_attr {
    # Called by attribute mutators. Gets as its first parameter the
    # name of the attribute to modify and as its second parameter the value
    # to store.
    my ($p, $attr, $val, $sth, $res);
    $p = shift;
    $attr = shift;
    $attr =~ s/^-//;
    $val = shift;

    $sth = $p->{-db}->prepare("UPDATE person SET $attr = ? WHERE id = ?");
    return $sth->execute($val, $p->{-id});
}

sub _ck_valid_keys {
    # Checks if all the keys of the hash reference received as the first
    # parameter are valid for their use in the database structure.
    #
    # Returns 0 if any extraneous key is received.
    # If all keys are valid, returns a value resulting from the bitwise addition
    # of what can be done with this set - Valid (1), insert (2), update (4),
    # use as full information (8)
    #
    # A set can be inserted as long as it has all the required values and does
    # not have any that should be system-generated (-id, -reg_time).
    #
    # A set can be used for a update as long as it is valid and does not have
    # the primary key on it (-id).
    #
    # A set is full information if it has all the valid keys. The absence of 
    # 'passwd' can be tolerated, as it cannot be retrieved from the database.
    #

    my (%hash, %valid, %ins, %no_ins, %no_upd, $ret);
    %hash = map { $_ => 1 } keys %{$_[0]};

    # All the valid keys
    %valid = (-id => 1, -firstname => 1, -famname => 1, -login => 1,
	      -nickname => 1, -passwd => 1, -org => 1, -dept => 1, -title => 1,
              -studies_id => 1, -email => 1, -person_type_id => 1,
              -postal_addr => 1, -zip => 1, -city => 1, -state_id => 1,
              -country_id => 1, -phone => 1, -fax => 1, -birth => 1,
              -resume => 1, -reg_time => 1, -pers_status_id => 1, -db => 1);

    # Keys required for insertion
    %ins  = (-firstname => 1, -famname => 1, -login => 1, -db => 1);

    # Invalid keys for insertion and update
    %no_ins = (-id => 1,  -reg_time => 1);
    %no_upd = (-id => 1, -login => 1, -db => 1);

    # We start assuming that the hash is insertable, updateable and complete.
    $ret = 15; # 15 = 8 | 4 | 2 | 1

    foreach my $k (keys %hash) {
	# Invalid key? Return immediately, it cannot be used for anything.
	return 0 unless $valid{$k};
    }

    foreach my $key (keys %ins) {
	$ret &= ~2 unless exists $hash{$key};
    }

    foreach my $key (keys %no_ins) {
        $ret &= ~2 if exists $hash{$key};
    }

    foreach my $key (keys %no_upd) {
	$ret &= ~4 if exists $hash{$key};
    }

    # Now check if full
    foreach my $key (keys %valid) {
	next if (exists $no_ins{$key} || exists $no_upd{$key});
	$ret &= ~8 unless exists $hash{$key};
    }

    return $ret;
}


1;

# $Log: Person.pm,v $
# Revision 1.26  2004/06/25 03:15:47  gwolf
# The system now notifies a person that he has submitted the maximum
# allowed number of proposals from the attendee's main menu
#
# Revision 1.25  2004/04/08 02:53:06  gwolf
# Little documentation fix
#
# Revision 1.24  2004/02/05 19:58:05  gwolf
# Permito búsquedas con regexp (en search, claro está)
#
# Revision 1.23  2003/12/20 04:14:51  mig
# - Agrego tags Id y Log que expanda el CVS
#
