# $Id: Common.pm,v 1.7 2004/11/12 05:44:36 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::Common
# Common functions for various Comas modules
######################################
# Depends on:
#
# No modules
package Comas::Common;

use strict;
use warnings;
use Carp;
require Exporter;
our (@ISA, @EXPORT_OK);

@ISA = qw(Exporter);
@EXPORT_OK = qw(valid_hash sql_insert sql_update);

=head1 NAME

Comas::Common - Common functions for various Comas modules

=head1 SYNOPSIS

This module is *NOT* provided for general human consumption. It provides the
following functions used directly by the other Comas modules:

%hash = valid_hash(@array)

Checks if a valid hash can be constructed from @array (i.e., that it has an
even number of elements, that no overlapping keys are found) and returns the
resulting array. If it is not valid, warns about it and returns undef.

=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

sub valid_hash {
    my (%hash);
    if (scalar @_ % 2) {
	carp 'Uneven number of elements';
	return undef;
    }
    while (scalar @_ >= 2) {
	my $key = shift;
	my $value = shift;
	if (exists $hash{$key}) {
	    carp "Duplicated key $key";
	    return undef;
	}
	$hash{$key} = $value;
    }
    return %hash;
}

sub sql_insert {
    # Will have 3 arguments:
    # The name of the table
    # The hash to insert
    # The master or valid hash
    
    # The last two arguments are passed to _ck_valid_keys, so reffer
    # to the documentation of _ck_valid_keys for the format of the hashes.

    my ($table, $hash, $valid) = (shift, shift, shift);
    my ($ck, $sql, @values, @keys, $sth, $id);

    unless (ref($hash->{-db})) {
	carp 'No valid database handler received:';
	return undef;
    }

    if (_ck_valid_hash($hash,$valid) & 2) {
        $sql = 'INSERT INTO '. $table .'(';                   
        foreach my $k (keys %$hash) {
            next if $k eq '-db';
            push(@values, $hash->{$k});
            $k =~ s/^-//;
            push(@keys, $k);
        }
        $sql .= join(', ', @keys);
        $sql .= ') VALUES (' . join(', ', map {'?'} @keys) . ')';

 	$hash->{-db}->begin_work;
	unless ($sth = $hash->{-db}->prepare($sql)) {
	    carp 'Could not prepare SQL insert query: ', 
	    $hash->{-db}->lastMsg;
	    $hash->{-db}->rollback;
	    return undef;
	}

	unless ($sth->execute(@values)) {
	    carp "Could not execute SQL insert query: ",
	    $hash->{-db}->lastMsg;
	    $hash->{-db}->rollback;
	    return undef;
	}
        
	unless ($sth = $hash->{-db}->prepare("SELECT
	    currval('".$table."_id_seq\')") and $sth->execute and ($id)
	    = $sth->fetchrow_array) {
	    carp "Could not query for the new ID: ", 
	    $hash->{-db}->lastMsg;
	    $hash->{-db}->rollback;
	    return undef;
	}

        $hash->{-db}->commit;
        return $id;

    }  else {
        carp 'Hash check failed';
        return undef;
    }
}


sub sql_update {
    # Will have 3 arguments:
    # The name of the table.
    # The hash with the values to insert, the id is taken as the
    # conditional to make the update.
    # The master or valid hash.
    
    # The last two arguments are passed to _ck_valid_keys, so reffer
    # to the documentation of _ck_valid_keys for the format of the hashes.

    my ($table, $hash, $valid) = (shift, shift, shift);
    my ($ck, $sql, @values, @keys, $sth, $id);

    unless (ref($hash->{-db})) {
	carp 'No valid database handler received:';
	return undef;
    }

    if (_ck_valid_hash($hash,$valid) & 4) {
        $sql = 'UPDATE '. $table .' SET ';
        foreach my $k (keys %$hash) {
            next if($k eq '-db' || $k eq '-id');
            push(@values, $hash->{$k});
            $k =~ s/^-//;
            push(@keys, $k);
        }
        push(@values, $hash->{-id});

        $sql .= join(", ", map {"$_ = ?"} @keys);
        $sql .= ' WHERE id = ?';

 	$hash->{-db}->begin_work;
	unless ($sth = $hash->{-db}->prepare($sql)) {
	    carp 'Could not prepare SQL update query: ', 
	    $hash->{-db}->lastMsg;
	    $hash->{-db}->rollback;
	    return undef;
	}

	unless ($sth->execute(@values)) {
	    carp "Could not execute SQL update query: ",
	    $hash->{-db}->lastMsg;
	    $hash->{-db}->rollback;
	    return undef;
	}
                
        $hash->{-db}->commit;
        return 1;
        
     } else {
         carp 'Invalid atributes';
         return undef;
     }
}

sub sql_select_id {
    # Will have 3 arguments:
    # The name of the table
    # The hash with the values to search
    # The master or valid hash
    
    # The last two arguments are passed to _ck_valid_keys, so reffer
    # to the documentation of _ck_valid_keys for the format of the hashes.

    # This function only returns an array with the id's of the result
    # of the select.

    my ($table, $hash, $valid) = (shift, shift, shift);
    my ($ck, $sql, @values, @keys, $sth, $id);

    unless (ref($hash->{-db})) {
	carp 'No valid database handler received:';
	return undef;
    }

    if (_ck_valid_hash($hash,$valid) & 1) {

        $sql = 'SELECT id FROM '. $table .' WHERE ';

        foreach my $k (keys %$hash) {
            next if($k eq '-db' || $k eq '-id');
            push(@values, $hash->{$k});
            $k =~ s/^-//;
            push(@keys, $k);
        }
        $sql .= join(" AND ", map {"$_ = ?"} @keys);
        print "$sql \n";

#  	$hash->{-db}->begin_work;
# 	unless ($sth = $hash->{-db}->prepare($sql)) {
# 	    carp 'Could not prepare SQL select query: ', 
# 	    $hash->{-db}->lastMsg;
# 	    $hash->{-db}->rollback;
# 	    return undef;
# 	}

# 	unless ($sth->execute(@values)) {
# 	    carp "Could not execute SQL select query: ",
# 	    $hash->{-db}->lastMsg;
# 	    $hash->{-db}->rollback;
# 	    return undef;
# 	}
                
#         $hash->{-db}->commit;
#         return 1;
        
     } else {
         carp 'Invalid atributes';
         return undef;
     }
}

sub sql_get_data {
    # Will have 3 arguments:
    # The name of the table
    # An array of id's to get the data.
    # A master hash or valid hash (we are going to get all the
    # attributes from it)

}

sub _ck_valid_hash {
    # Gets 2 arguments, the hash to test and the valid hash.

    # In the hash to test the keys are the name of the attributes, and
    # the values the values that we are going to insert.


    # The valid or master hash have to have the name of all the valid
    # atributtes in the keys, and the value of them are a bitwise
    # addition of what we can do with the value:

    # 1 - Can exist for insert
    # 2 - Must exist for insert
    # 4 - Can exist for update 
    # 8 - Must exist for update
    
    # 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)

    my ($hash, $valid, $ret);
    
    $hash = shift;    
    $valid = shift;

    # We start assuming that the hash is insertable, updateable and complete.
    $ret = 15; # 15 = 8 | 4 | 2 | 1
    
    # We will check every hash key if they can be inserted or updated
    # agains the values of valid
    
    foreach my $k (keys %{$hash}) {
        next if ($k eq '-db');

        $hash->{$k} =~ s/^\ +//;

        # Invalid key? Return immediately, it cannot be used for anything.
        unless($valid->{$k}) {
            carp 'Invalid key: '.$k;
            return 0;
        }

        if($hash->{$k} =~ /^\s*$/) {
            carp 'Invalid value at key: '.$k;
            return 0;
        }

        # Check if the key can be inserted.
        $ret &= ~2 unless($valid->{$k} & 1);

        # Check if the key can be updated.
        $ret &= ~4 unless($valid->{$k} & 4);
    }

    # Now we go in the other way, checking valid against hash if the
    # keys required for insertion or update exist.

    foreach my $k (keys %{$valid}) {
        # Check if the key is required for an insert.
        if ($valid->{$k} & 2) {
            $ret &= ~2 unless($hash->{$k});
        }

        # Check if the key is required for an updated.
        if ($valid->{$k} & 8) {
            $ret &= ~4 unless($hash->{$k});
        }
    }

    # Now we know what we can do with the hash, so chech if its
    # complete based on the operation for who is suitable :)

    foreach my $k (keys %{$valid}) {
        if ($ret & 2) {
            $ret &= ~8 unless(exists $hash->{$k} && $valid->{$k} &~ 1);
        } elsif ($ret & 4) {
            $ret &= ~8 unless(exists $hash->{$k} && $valid->{$k} &~ 4);
        } else {
            # If the hash is not insertable or updatable, it cant be complete
            $ret &= ~8;
        }
    }

    return $ret;
}


1;

# $Log: Common.pm,v $
# Revision 1.7  2004/11/12 05:44:36  gwolf
# Added the functionality to be able to handle more than one file per proposal and store it in the DB, not in separate files.
#
# Revision 1.6  2003/12/20 04:14:51  mig
# - Agrego tags Id y Log que expanda el CVS
#
