<?php

/*
** Fishlib - a collection of utilities for db-driven applications in PHP
** Copyright (C) 2002  LTWD, LLC DBA The Madfish Group
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Lesser General Public
** License as published by the Free Software Foundation; either
** version 2.1 of the License, or (at your option) any later version.
**
** This library 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
** Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public
** License along with this library; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

// this is a reference to the Pear DB library
require_once('DB.php');

require_once(
'fishdsn.class.php');
require_once(
'fishargs.class.php');
require_once(
'fisherror.class.php');

// FishError::set_handler(E_ALL, H_DEBUG);

function db_error_handler($db_error)
{

    
// $skip_past_function = 'mysqlraiseerror';
    
$error_msg 'message: '.$db_error->message
        
' userinfo: '.$db_error->userinfo
    
;
    
error_log("db_error_handler: error_msg={$error_msg}");

    
FishDB::attempt_rollback();

    if (
strlen($error_msg) > 1000)
    {
        
$error_msg substr($error_msg0500).'...'.substr($error_msg,-500);
    }

    
$timestamp time();
    
$private_error "DB error ($timestamp): $error_msg";
    
$error_level E_USER_ERROR;
    
error_log($private_error);
print 
"<li>$private_error\n";
    
user_error("Database error - please contact the system administrator. ($timestamp)",$error_level);
}

class 
FishDB
{
    function 
db_options($k=NULL)
    {
        
// from PEAR DB/common.php
        
static $options = array(
            
'persistent' => false,
            
'optimize' => 'performance',
            
'debug' => 0,
            
'seqname_format' => '%s_seq',
            
'autofree' => false
        
);
        if (
$k === NULL)
            return 
$options;
        if (isset(
$options[$k]))
            return 
$options[$k];
        return 
FALSE;
    }

    function &
db_connect()
    {
        static 
$_connections = array();
        static 
$_defaults = array(
            
'application' => NULL
            
'dsnpath' => NULL
            
'database' => NULL
            
'username' => NULL
            
'db_error_level' => E_USER_ERROR
            
'db_error_handler' => 'db_error_handler'
            
'options' => array(
                
'debug' => 4
                
'persistent' => FALSE
                
'autofree' => TRUE
            
)
            , 
'check_existing' => FALSE
        
);

        static 
$_simple = array('application','username','password');

        
$dc count($_connections);
        
$p func_get_args();
        if (empty(
$p))
        {
            if (
$dc)
            {
                
$dbh array_pop(array_values($_connections));
                if (
$dbh === NULL)
                {
                    
user_error('Last connection is NULL.'E_USER_WARNING);
                    return 
FALSE;
                }
                elseif (!
is_object($dbh) || !is_a($dbh,'DB_common'))
                {
                    
$private_error 'invalid connection value: dbh='
                        
var_export($dbhTRUE)
                    ;
                    
user_error('Last connection is invalid.'E_USER_WARNING);
                    return 
FALSE;
                }
                return 
$dbh;
            }
            
user_error('No existing database connection found.'E_USER_WARNING);
            return 
FALSE;
        }

        
$p FishArgs::parse_arguments($p$_simple$_defaults);

        if (empty(
$p['application']))
        {
            
$p['application'] = $p['database'];
            if (!empty(
$p['username']))
            {
                
$p['application'] .= ':'.$p['username'];
            }
        }

        
$dbh FishUtil::array_key_value($_connections,$p['application'],NULL);
        if (
$dbh !== NULL && is_object($dbh) && is_a($dbh,'DB_common'))
        {
            return 
$dbh;
        }
        if (
$p['check_existing'])
            return 
FALSE;

        
$dsn FishDSN::db_dsnlist($p);
        unset(
$dsn['options']);
        if (empty(
$p['options']))
            
$p['options'] = array();
        
$options FishDB::db_options();
        foreach (
array_keys($dsn) as $k)
        {
            if (
$dsn[$k] === NULL)
            {
                unset(
$dsn[$k]);
            }
            elseif (isset(
$options[$k]))
            {
                if (empty(
$p['options'][$k]))
                    
$p['options'][$k] = $dsn[$k];
                unset(
$dsn[$k]);
            }
        }
//        FishError::trace('dsn=', $dsn, ' options=', $p['options']);
        
$dbh =& DB::connect($dsn$p['options']);
        if (
DB::isError($dbh))
        {
            
$private_error 'dsn:'.var_export($dsn,TRUE)."\n"
                
.' error:'.var_export($dbh,TRUE)."\n"
            
;
            
user_error(
                
'Could not connect to database: '.$dbh->getMessage()
                , 
$p['db_error_level']
            );
            return 
NULL;
        }

        
// anything that's open - sorry
        
$dbh->query('rollback');

        
$dbh->setFetchMode(DB_FETCHMODE_ASSOC);
        if (
is_string($p['db_error_handler']) 
            && 
function_exists($p['db_error_handler'])
        )
        {
            
// it's a function name - OK
        
}
        elseif (
is_array($p['db_error_handler']) 
            && 
count($p['db_error_handler']) == 
            
&& method_exists($p['db_error_handler'][0], $p['db_error_handler'][1]) 
        ) 
        {
            
// it's an object method - OK
        
}
        else
        {
            
$p['db_error_handler'] = NULL;
        }
        if (!empty(
$p['db_error_handler']))
        {
            
$dbh->setErrorHandling(PEAR_ERROR_CALLBACK,$p['db_error_handler']);
        }
        else
        {
            
$dbh->setErrorHandling(PEAR_ERROR_TRIGGER,$p['db_error_level']);
        }

        
$_connections[$p['application']] = $dbh;
        if (
$dbh === NULL)
        {
            
$private_error var_export($_connectionTRUE);
            
user_error('connection is NULL.'$p['db_error_level']);
            return 
FALSE;
        }
        elseif (!
is_object($dbh) || !is_a($dbh,'DB_common'))
        {
            
$private_error 'invalid connection value: dbh='
                
var_export($dbhTRUE)
            ;
//            FishError::trace($private_error);
            
user_error('connection is invalid.'E_USER_WARNING);
            return 
FALSE;
        }
        return 
$dbh;
    }



    
// array db_values_array ([string table name [, string value field [, string label field [, string sort field [, string where clause]]]]])

    // This function builds an associative array out of the values in
    // the MySQL table specified in the first argument. The data from the column 
    // named in the second argument will be set to the keys of the array.
    // If the third argument is not empty, the data from the column it names
    // will be the values of the array; otherwise, the values will be equal
    // to the keys. If the third argument is not empty, the data will be
    // ordered by the column it names; otherwise, it will be ordered by
    // the key column. The optional fourth argument specifies any additional
    // qualification for the query against the database table; if it is empty,
    // all rows in the table will be retrieved.

    // If either the first or second argument is empty, no query is run and
    // an empty array is returned.

    // The function presumes that whoever calls it knows what they're about -
    // e.g., that the table exists, that all the column names are correct, etc.

    
function db_values_array ()
    {
        static 
$_defaults = array(
            
'label' => NULL
            
'table' => NULL
            
'value' => NULL
            
'sort' => NULL
            
'where' => NULL
            
'input' => NULL
        
);
        static 
$_simple = array('label','table');
        
$p func_get_args();
        
extract($_defaults);
        
$p FishArgs::parse_arguments($p$_simple$_defaults);
        
extract($pEXTR_IF_EXISTS);

        if (empty(
$label))
        {
            
$label str_replace('_id','',$value);
        }
        elseif (empty(
$value))
        {
            
$value $label.'_id';
        }
        if (empty(
$table))
        {
            
$table $label.'s';
        }
        if (empty(
$sort))
        {
            
$sort $label;
        }
        if (empty(
$where))
        {
            
$where '1=1';
        }

        
$dbh FishDB::db_connect();
        if (!empty(
$input) && is_array($input))
        {
            
$output $input;
        }
        
$result $dbh->query(
            
'select !, ! from ! where ! order by !'
            
, array($value,$label,$table,$where,$sort)
        );
        while (
$row $result->fetchRow(DB_FETCHMODE_ASSOC))
        {
            
$output[$row[$value]] = $row[$label];
        }
        
$result->free();
// print "<li>query: {$dbh->last_query}\n";
        
return $output;
    }

    function 
db_fetch_record()
    {
        static 
$_defaults = array(
            
'table' => NULL
            
'key' => NULL
            
'value' => NULL
            
'columns' => '*'
            
'extra' => NULL
            
'key_op' => '='
            
'key_join' => ' and '
            
'order_by' => NULL
        
);
        static 
$_simple = array('table''key''value');
        
$args func_get_args();
        
extract($_defaults);
        
$p FishArgs::parse_arguments($args$_simple$_defaults);
        
extract($pEXTR_IF_EXISTS);

        
$query 'select ! from !';
        
$bind = array($columns,$table);
        
$where NULL;
        if (!empty(
$key) && !empty($value))
        {
            
$where .= implode($key_joinarray_fill(0count($key), "! $key_op ?"));
            if (
is_array($key) && is_array($value))
            {
                foreach (
$key as $i => $k)
                {
                    
$bind[] = $k;
                    
$bind[] = $value[$i];
                }
            }
            else
            {
                
$bind[] = $key;
                
$bind[] = $value;
            }
        }
        if (
$extra)
        {
            if (
$where)
            {
                
$where " ($where) and ";
            }
            
$where .= " ($extra) ";
        }
        if (
$where)
        {
            
$query .= ' where '.$where;
        }
        
$order_by = (array)$order_by;
        if (
count($order_by) > 0)
        {
            
$query .= ' order by '.join(',',$order_by);
        }
        
$dbh FishDB::db_connect();
        
$result $dbh->getAll($query$bindDB_FETCHMODE_ASSOC);
        if (!
$result)
        {
            
$private_error 'could not fetch record: '
                
.' query='.$query
                
.' bind='.$bind
                
.' result='.$result
            
;
            
user_error("Could not fetch $table record"E_USER_ERROR);
            exit;
        }
        if (
count($result) == 1)
        {
            
$result array_shift($result);
        }
        return 
$result;
    }

    function 
nullop($value=NULL,$op='=')
    {
        
$inop $op;
        if (
$value === NULL)
        {
            if (
strstr($op,'!='))
            {
                
$op 'is not';
            }
            else
            {
                
$op 'is';
            }
        }
        else
        {
            if (
strstr($op'!='))
            {
                
$op '<>';
            }
        }
//FishError::trace('value:', $value, ' inop:', $inop, ' op:', $op);
        
return $op;
    }

    function 
attempt_rollback()
    {
        
// this should be unnecessary but can't hurt
        
$dbh FishDB::db_connect(array('check_existing'=>TRUE));
        if (
$dbh === FALSE)
            return 
FALSE;

        
$dbhtype gettype($dbh);
        if (
$dbhtype == 'object')
            
$dbhclass strtolower(get_class($dbh));
        else
            
$dbhclass '';
        if (
strpos($dbhclass'db_') !== FALSE && strpos($dbhclass'error') === FALSE)
        {
            
$last_query $dbh->last_query;
            
error_log("rolling back transaction: last query: $last_query");
            
$dbh->query('rollback');
        }
        else
        {
            
error_log("db_error_handler: dang, dbh is a {$dbhtype} {$dbhclass}");
        }
    }
}
?>