<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
 * SugarCRM is a customer relationship management program developed by
 * SugarCRM, Inc. Copyright (C) 2004 - 2007 SugarCRM Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License version 3 as published by the
 * Free Software Foundation with the addition of the following permission added
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 * 
 * 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, see http://www.gnu.org/licenses or write to the Free
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 * 
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU General Public License version 3,
 * these Appropriate Legal Notices must retain the display of the "Powered by
 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
 * technical reasons, the Appropriate Legal Notices must display the words
 * "Powered by SugarCRM".
 ********************************************************************************/
/*********************************************************************************

* Description: This file handles the Data base functionality for the application.
* It acts as the DB abstraction layer for the application. It depends on helper classes
* which generate the necessary SQL. This sql is then passed to PEAR DB classes.
* The helper class is chosen in DBManagerFactory, which is driven by 'db_type' in 'dbconfig' under config.php.
*
* All the functions in this class will work with any bean which implements the meta interface.
* The passed bean is passed to helper class which uses these functions to generate correct sql.
*
* The meta interface has the following functions:
* getTableName()                Returns table name of the object.
* getFieldDefinitions()         Returns a collection of field definitions in order.
* getFieldDefintion(name)       Return field definition for the field.
* getFieldValue(name)           Returns the value of the field identified by name.
*                               If the field is not set, the function will return boolean FALSE.
* getPrimaryFieldDefinition()   Returns the field definition for primary key
*
* The field definition is an array with the following keys:
*
* name      This represents name of the field. This is a required field.
* type      This represents type of the field. This is a required field and valid values are:
*           �   int
*           �   long
*           �   varchar
*           �   text
*           �   date
*           �   datetime
*           �   double
*           �   float
*           �   uint
*           �   ulong
*           �   time
*           �   short
*           �   enum
* length    This is used only when the type is varchar and denotes the length of the string.
*           The max value is 255.
* enumvals  This is a list of valid values for an enum separated by "|".
*           It is used only if the type is �enum�;
* required  This field dictates whether it is a required value.
*           The default value is �FALSE�.
* isPrimary This field identifies the primary key of the table.
*           If none of the fields have this flag set to �TRUE�,
*           the first field definition is assume to be the primary key.
*           Default value for this field is �FALSE�.
* default   This field sets the default value for the field definition.
*
*
* Portions created by SugarCRM are Copyright (C) SugarCRM, Inc.
* All Rights Reserved.
* Contributor(s): ______________________________________..
********************************************************************************/

//Technically we can port all the functions in the latest bean to this file
// that is what PEAR is doing anyways.

require_once('include/database/MysqlManager.php');

class MysqliManager extends MysqlManager
{
    var $dbType = 'mysql';
    function MysqliManager(){
        parent::MysqlManager();
    }
    /**
     * checks for errors and will either log or die depending on the dieOnError
     *
     * @param STRING $msg - message to log
     * @param BOOLEAN $dieOnError - should die on error
     * @return BOOLEAN - error occured returns true
     */
    function checkError($msg='', $dieOnError=false){
        if (mysqli_errno($this->database)){
            if($this->dieOnError || $dieOnError){
                $GLOBALS['log']->fatal("MySQL error ".mysqli_errno($this->database).": ".mysqli_connect_error());
                sugar_die ($msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_connect_error());
            }else{
                $this->last_error = $msg."MySQL error ".mysqli_errno($this->database).": ".mysqli_connect_error();
                $GLOBALS['log']->error("MySQL error ".mysqli_errno($this->database).": ".mysqli_connect_error());

            }
            return true;
        }
        return false;
    }

    function bind_query($sql,$literals, $dieOnError=false, $msg='', $suppress=false) {

        global $sql_queries;
        $sql_queries++;
        $GLOBALS['log']->info('Query:' . $sql);
        $this->checkConnection();

        $bind_types="";
        $bind_values=array();
        $bind_values_ref="";
        $ctr=0;
        foreach ($literals as $position=>$value) { 
            $bind_value=unquote_db_literal($value);       
            $sql=substr_replace ( $sql,'?', $position, strlen($value));
            $type="s";
            if (is_numeric($bind_value)) {
                $type="i";
                if (strstr($bind_value,'.') !== false) {
                    $type='d';
                }
                $bind_values[]=$bind_value;
            }else {
                $bind_values[]="'".$bind_value."'";
            }
            $bind_types.=$type;
            $bind_values_ref.=',';
            $bind_values_ref.="\$bind_values[$ctr]";
            $ctr++;
        }
        //$this->freeResult();
        $this->query_time = microtime();
        $this->lastsql = $sql;
        if($suppress==true){












        } else {
            $stmt = mysqli_prepare($this->database,$sql);
            $bind_call="mysqli_stmt_bind_param(\$stmt,'".$bind_types."'". $bind_values_ref . ");";
            eval($bind_call);
            mysqli_stmt_execute($stmt);
            $result=mysqli_store_result($this->database);
        }

        $this->lastmysqlrow = -1;

        $this->query_time = microtime_diff($this->query_time, microtime());
        $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
        $this->dump_slow_queries($sql);
        $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
        if($autofree){
            $this->lastResult[] =& $result;
        }
        return $result;
    }

    /**
     * Performs database query given an sql string and returns a mysql result
     *
     * @param STRING $sql -  query to be handled 
     * @param boolean $dieOnError - exit if an error occurs
     * @param string $msg - string to log if an error occurs
     * @param boolean  $suppress - suppress error reporting 
     * @return mysql_result
     */
    function query($sql, $dieOnError=false, $msg='', $suppress=false, $autofree=false){
		static $queryMD5 = array();
        $literals=$this->parse_query($sql);
        if (!empty($literals)) {
            return $this->bind_query($sql,$literals, $dieOnError, $msg, $suppress);
        }

        global $sql_queries;
        $sql_queries++;
        $GLOBALS['log']->info('Query:' . $sql);
        $this->checkConnection();
        //$this->freeResult();
        $this->query_time = microtime();
        $this->lastsql = $sql;
        if($suppress==true){








        } else {
            $result = mysqli_query($this->database,$sql);
        }
        $md5 = md5($sql);
        
        if(!empty($queryMD5[$md5])){
        	//_pp('Duplicate Query:' . $sql,false, true);
        	//display_stack_trace();
        }else{
        	$queryMD5[$md5] = true;
        }
        
        $this->lastmysqlrow = -1;

        $this->query_time = microtime_diff($this->query_time, microtime());
        $GLOBALS['log']->info('Query Execution Time:'.$this->query_time);
        $this->dump_slow_queries($sql);
        $this->checkError($msg.' Query Failed:' . $sql . '::', $dieOnError);
        if($autofree){
            $this->lastResult[] =& $result;
        }
        return $result;
    }

    function freeResult($result=false){
        if(!$result && $this->lastResult){
            $result = current($this->lastResult);
            while($result){
                mysqli_freeresult($result);
                $result = next($this->lastResult);
            }
            $this->lastResult = array();
        }
        if($result){
            mysqli_freeresult($result);
        }
    }


    function getOne($sql, $dieOnError=false, $msg=''){
        $GLOBALS['log']->info("Get One: . |$sql|");
        $this->checkConnection();
        $queryresult = $this->query($sql, $dieOnError, $msg);
        if (!$queryresult) $result = false;
        else {
            $result = mysqli_data_seek($queryresult,0);
        }
        $this->checkError($msg.' Get One Failed:' . $sql . '::', $dieOnError);
        $row = mysqli_fetch_row($queryresult);
        return !empty($row[0]) ? $row[0] : false;
    }

    /**
     * Returns the description of fields based on the result
     *
     * @param RESULT RESOURCE $result
     * @param boolean $make_lower_case
     * @return ARRAY - field array
     */
    function getFieldsArray($result, $make_lower_case=false)
    {
        $field_array = array();

        if(! isset($result) || empty($result))
        {
            return 0;
        }
        $i = 0;
        while ($i < mysqli_num_fields($result))
        {

            $meta = mysqli_fetch_field_direct ($result, $i);

            if (!$meta)
            {
                return 0;
            }

            array_push($field_array,$meta->name);

            $i++;
        }

        return $field_array;

    }

    /**
     * Returns the number of rows returned by the result
     *
     * @param RESULT RESOURCE $result
     * @return int
     */
    function getRowCount(&$result){
        if(isset($result) && !empty($result)){
            return mysqli_num_rows($result);
        }
        return 0;



    }

    /**
     * Returns the number of rows affected
     *
     * @return INT
     */
    function getAffectedRowCount($result = null){
        return mysqli_affected_rows($this->database);
    }

    /**
     * fetchs the associative array from a database result
     *
     * @param RESULT RESOURCE $result - database result
     * @param ROW NUMBER $rowNum - row number
     * @param BOOLEAN $encode - convert everything for html display
     * @return ARRAY - associative array
     */
    function fetchByAssoc(&$result, $rowNum = -1, $encode=true){
        if(!$result)return false;
        if($result && $rowNum > -1){
            if($this->getRowCount($result) > $rowNum){
                mysqli_data_seek($result, $rowNum);
            }
            $this->lastmysqlrow = $rowNum;
        }

        $row = mysqli_fetch_assoc($result);

        if($encode && $this->encode && is_array($row))return array_map('to_html', $row);
        return $row;
    }

    /**
     * Encodes a string for storing in the database
     *
     * @param STRING $string
     * @param unknown_type $isLike
     * @return STRING
     */
    function quote($string,$isLike=true){
        global $sugar_config;
        $string = from_html($string);
        $string = mysqli_escape_string($this->database,$string);
        return $string;
    }

    /**
     * Encodes a string for storing in the database
     *
     * @param STRING $string
     * @param unknown_type $isLike
     * @return STRING
     */
    function quoteForEmail($string,$isLike=true){
        global $sugar_config;
        $string = mysqli_escape_string($this->database,$string);
        return $string;
    }
    
    /**
     * will quote the strings of the passed in array
     * The array must only contain strings
     *
     * @param ARRAY $array
     * @param unknown_type $isLike
     */
    function arrayQuote(&$array, $isLike=true) {
        for($i = 0; $i < count($array); $i++){
            $array[$i] = MysqliManager::quote($array[$i], $isLike);
        }
    }
    /**
     * Takes in the database settings and opens a database connection based on those
     * will open either a persistent or non-persistent connection.
     * If a persistent connection is desired but not available it will defualt to non-persistent
     * 
     * configOptions must include
     * db_host_name - server ip 
     * db_user_name - database user name
     * db_password - database password
     *
     * @param ARRAY $configOptions - array of options 
     *  
     * @param boolean $dieOnError
     */
    function connect($configOptions = false, $dieOnError = false){
        global $sugar_config;

        if (!$configOptions) {
            $configOptions = $sugar_config['dbconfig'];
        }
        if ($sugar_config['dbconfigoption']['persistent'] == true) {
            $this->database =@mysqli_connect($configOptions['db_host_name'],$configOptions['db_user_name'],$configOptions['db_password']);
        }

        if(!$this->database){
            $this->database = mysqli_connect($configOptions['db_host_name'],$configOptions['db_user_name'],$configOptions['db_password']) or sugar_die("Could not connect to server ".$configOptions['db_host_name']." as ".$configOptions['db_user_name'].".".mysqli_connect_error());
            if($this->database  && $sugar_config['dbconfigoption']['persistent'] == true){
                $_SESSION['administrator_error'] = "<B>Severe Performance Degradation: Persistent Database Connections not working.  Please set \$sugar_config['dbconfigoption']['persistent'] to false in your config.php file</B>";
            }
        }
        @mysqli_select_db($this->database,$configOptions['db_name']) or sugar_die( "Unable to select database: " . mysqli_connect_error());
        
        // cn: using direct calls to prevent this from spamming the Logs
        mysqli_query($this->database,"SET CHARACTER SET utf8"); // no quotes around "[charset]"
        mysqli_query($this->database,"SET NAMES 'utf8'");

        if($this->checkError('Could Not Connect:', $dieOnError))
        $GLOBALS['log']->info("connected to db");

//        $GLOBALS['log']->info("Connect:".$this->database);

    }

    /**
     * Frees Results and disconnects the database
     *
     */
    function disconnect() {
        if(isset($this->database)){
            $this->freeResult();
            mysqli_close($this->database);
            unset($this->database);
        }
    }
    /*
     * function parses query for literal strings surrounded by global delimeters $GLOBALS['LITERAL_DELIM_START'] and $GLOBALS['LITERAL_DELIM_END']
     * if these literal string exist in the query the function returns an array of those strings and then position in the array (last match first)
     * else the function returns false.
     */
    function parse_query($query) {
        global $sugar_config;
        if (!isset($sugar_config['prepared_statements_enabled']) or empty($sugar_config['prepared_statements_enabled'])) {
            return false;
        }
        
        $matches=array();
            
        if (!empty($GLOBALS['LITERAL_DELIM_START']) and !empty($GLOBALS['LITERAL_DELIM_END'])) {
            //U modifiers makes the pattern ungreedy. <<<abc>>> <<<def>>> returns 2 different matches
            $literal_exp = "/" . $GLOBALS['LITERAL_DELIM_START'] .  ".+" . $GLOBALS['LITERAL_DELIM_END'] . "/U";

            preg_match_all($literal_exp,$query,$matches,PREG_OFFSET_CAPTURE);
            if (is_array($matches) and count($matches) > 0 and is_array($matches[0])) {
                $matches=array_reverse($matches[0]);
                $value_pos_array=array();
                foreach ($matches as $value_pos) {
                    $value_pos_array[$value_pos[1]]=$value_pos[0];
                }
                return $value_pos_array;
            }
        }
        return false;
    }
    
    /**
     * Returns an array of table for this database
     * @return	$tables		an array of with table names
     * @return	false		if no tables found
     */
    function getTablesArray() {
        global $sugar_config;
        $GLOBALS['log']->debug('PearDatabase fetching table list');

        $this->checkConnection();

        if($this->database) {
            $tables = array();


            $r = $this->query('SHOW TABLES');
            if( $r instanceOf mysqli_result ) {
                while($a = $this->fetchByAssoc($r)) {
                    $row = array_values($a);
                    $tables[]=$row[0];
                }
                return $tables;
            }
        }


        return false; // no database available
    }

}

?>
