mirror of
				https://github.com/librenms/librenms.git
				synced 2024-10-07 16:52:45 +00:00 
			
		
		
		
	* Initial start on new templating support * further updates * more updates * More working version * Last fixes * Small updates * modified test to look for dbname. * Schema update * Added fix for not being able to disable Philips Hue transport * Updated sql file * Updated db_schema * Set myclabs/deep-copy to be 1.7.x version in composer * Fixes from murrant * Forced nikic/php-parser to version 3.1.x in composer * Updated composer to use custom fork of string-blade-compiler + fixed transport use * Updated to always use correct template * Merged legacy and blade templating engines * Removed template type + fixed test-alert.php * Added more template placeholders * Added ability to reference data in templates as $alert->X * Updated docs for templates * Updated db_schema.yaml * Added $alert->builder * Clarify the use of $alert->builder * Fixed the use of $alert->transport * renamed schema file * Added template validation * Small update to fix travis issue * Add Docs text to title bar * Updated amqp to new of version * Consistency in alert rule and template title bars
		
			
				
	
	
		
			753 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			753 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
/*
 | 
						|
 * dbFacile - A Database API that should have existed from the start
 | 
						|
 * Version 0.4.3
 | 
						|
 *
 | 
						|
 * This code is covered by the MIT license http://en.wikipedia.org/wiki/MIT_License
 | 
						|
 *
 | 
						|
 * By Alan Szlosek from http://www.greaterscope.net/projects/dbFacile
 | 
						|
 *
 | 
						|
 * The non-OO version of dbFacile. It's a bit simplistic, but gives you the
 | 
						|
 * really useful bits in non-class form.
 | 
						|
 *
 | 
						|
 * Usage
 | 
						|
 * 1. Connect to MySQL as you normally would ... this code uses an existing connection
 | 
						|
 * 2. Use dbFacile as you normally would, without the object context
 | 
						|
 * 3. Oh, and dbFetchAll() is now dbFetchRows()
 | 
						|
 */
 | 
						|
 | 
						|
use LibreNMS\Config;
 | 
						|
use LibreNMS\Exceptions\DatabaseConnectException;
 | 
						|
 | 
						|
function dbIsConnected()
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    if (empty($database_link)) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    return mysqli_ping($database_link);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Connect to the database.
 | 
						|
 * Will use global $config variables if they are not sent: db_host, db_user, db_pass, db_name, db_port, db_socket
 | 
						|
 *
 | 
						|
 * @param string $db_host
 | 
						|
 * @param string $db_user
 | 
						|
 * @param string $db_pass
 | 
						|
 * @param string $db_name
 | 
						|
 * @param string $db_port
 | 
						|
 * @param string $db_socket
 | 
						|
 * @return mysqli
 | 
						|
 * @throws DatabaseConnectException
 | 
						|
 */
 | 
						|
function dbConnect($db_host = null, $db_user = '', $db_pass = '', $db_name = '', $db_port = null, $db_socket = null)
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
 | 
						|
    if (dbIsConnected()) {
 | 
						|
        return $database_link;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!function_exists('mysqli_connect')) {
 | 
						|
        throw new DatabaseConnectException("mysqli extension not loaded!");
 | 
						|
    }
 | 
						|
 | 
						|
    if (is_null($db_host)) {
 | 
						|
        $db_config = Config::getDatabaseSettings();
 | 
						|
        extract($db_config);
 | 
						|
        /** @var string $db_host */
 | 
						|
        /** @var string $db_port */
 | 
						|
        /** @var string $db_socket */
 | 
						|
        /** @var string $db_name */
 | 
						|
        /** @var string $db_user */
 | 
						|
        /** @var string $db_pass */
 | 
						|
    }
 | 
						|
 | 
						|
    if (empty($db_socket)) {
 | 
						|
        $db_socket = null;
 | 
						|
    }
 | 
						|
    if (!is_numeric($db_port)) {
 | 
						|
        $db_port = null;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!$db_host && !$db_socket) {
 | 
						|
        throw new DatabaseConnectException("Database configuration not configured");
 | 
						|
    }
 | 
						|
 | 
						|
    $database_link = @mysqli_connect('p:' . $db_host, $db_user, $db_pass, null, $db_port, $db_socket);
 | 
						|
    if ($database_link === false) {
 | 
						|
        $error = mysqli_connect_error();
 | 
						|
        if ($error == 'No such file or directory') {
 | 
						|
            $error = 'Could not connect to ' . $db_host;
 | 
						|
        }
 | 
						|
        throw new DatabaseConnectException($error);
 | 
						|
    }
 | 
						|
 | 
						|
    mysqli_options($database_link, MYSQLI_OPT_LOCAL_INFILE, false);
 | 
						|
 | 
						|
    $database_db = mysqli_select_db($database_link, $db_name);
 | 
						|
    if (!$database_db) {
 | 
						|
        $db_create_sql = "CREATE DATABASE $db_name CHARACTER SET utf8 COLLATE utf8_unicode_ci";
 | 
						|
        mysqli_query($database_link, $db_create_sql);
 | 
						|
        $database_db = mysqli_select_db($database_link, $db_name);
 | 
						|
    }
 | 
						|
 | 
						|
    if (!$database_db) {
 | 
						|
        throw new DatabaseConnectException("Could not select database: $db_name. " . mysqli_error($database_link));
 | 
						|
    }
 | 
						|
 | 
						|
    dbQuery("SET NAMES 'utf8'");
 | 
						|
    dbQuery("SET CHARACTER SET 'utf8'");
 | 
						|
    dbQuery("SET COLLATION_CONNECTION = 'utf8_unicode_ci'");
 | 
						|
 | 
						|
    return $database_link;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Performs a query using the given string.
 | 
						|
 * Used by the other _query functions.
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbQuery($sql, $parameters = array())
 | 
						|
{
 | 
						|
    global $fullSql, $debug, $database_link, $config;
 | 
						|
    $fullSql = dbMakeQuery($sql, $parameters);
 | 
						|
    if ($debug) {
 | 
						|
        $fullSql = str_replace(PHP_EOL, '', $fullSql);
 | 
						|
 | 
						|
        // hide binary field updates and inserts
 | 
						|
        $fullSql = preg_replace("/(.*alert_log.*details[` ]*= *')[^']*('.*)/i", '$1<binary data>$2', $fullSql);
 | 
						|
        if (class_exists('Log')) {
 | 
						|
            Log::info("SQL[%y$fullSql%n]", ['color' => true]);
 | 
						|
        } else {
 | 
						|
            c_echo("SQL[%y$fullSql%n] \n");
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    $result = mysqli_query($database_link, $fullSql);
 | 
						|
    if (!$result) {
 | 
						|
        $mysql_error = mysqli_error($database_link);
 | 
						|
        if (isset($config['mysql_log_level']) && ((in_array($config['mysql_log_level'], array('INFO', 'ERROR')) && !preg_match('/Duplicate entry/', $mysql_error)) || in_array($config['mysql_log_level'], array('DEBUG')))) {
 | 
						|
            if (!empty($mysql_error)) {
 | 
						|
                $error_msg =  "MySQL Error: $mysql_error ($fullSql)";
 | 
						|
                c_echo("%R$error_msg%n\n", isCli() || $debug);
 | 
						|
                logfile(date($config['dateformat']['compact']) . ' ' . $error_msg);
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return $result;
 | 
						|
}//end dbQuery()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Passed an array and a table name, it attempts to insert the data into the table.
 | 
						|
 * Check for boolean false to determine whether insert failed
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbInsert($data, $table)
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    $time_start = microtime(true);
 | 
						|
 | 
						|
    // the following block swaps the parameters if they were given in the wrong order.
 | 
						|
    // it allows the method to work for those that would rather it (or expect it to)
 | 
						|
    // follow closer with SQL convention:
 | 
						|
    // insert into the TABLE this DATA
 | 
						|
    if (is_string($data) && is_array($table)) {
 | 
						|
        $tmp   = $data;
 | 
						|
        $data  = $table;
 | 
						|
        $table = $tmp;
 | 
						|
        // trigger_error('QDB - Parameters passed to insert() were in reverse order, but it has been allowed', E_USER_NOTICE);
 | 
						|
    }
 | 
						|
 | 
						|
    $sql = 'INSERT INTO `'.$table.'` (`'.implode('`,`', array_keys($data)).'`)  VALUES ('.implode(',', dbPlaceHolders($data)).')';
 | 
						|
 | 
						|
    dbBeginTransaction();
 | 
						|
    $result = dbQuery($sql, $data);
 | 
						|
    if ($result) {
 | 
						|
        $id = mysqli_insert_id($database_link);
 | 
						|
        dbCommitTransaction();
 | 
						|
        // return $id;
 | 
						|
    } else {
 | 
						|
        dbRollbackTransaction();
 | 
						|
        $id = null;
 | 
						|
    }
 | 
						|
 | 
						|
    recordDbStatistic('insert', $time_start);
 | 
						|
    return $id;
 | 
						|
}//end dbInsert()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Passed an array and a table name, it attempts to insert the data into the table.
 | 
						|
 * $data is an array (rows) of key value pairs.  keys are fields.  Rows need to have same fields.
 | 
						|
 * Check for boolean false to determine whether insert failed
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbBulkInsert($data, $table)
 | 
						|
{
 | 
						|
    $time_start = microtime(true);
 | 
						|
    // the following block swaps the parameters if they were given in the wrong order.
 | 
						|
    // it allows the method to work for those that would rather it (or expect it to)
 | 
						|
    // follow closer with SQL convention:
 | 
						|
    // insert into the TABLE this DATA
 | 
						|
    if (is_string($data) && is_array($table)) {
 | 
						|
        $tmp   = $data;
 | 
						|
        $data  = $table;
 | 
						|
        $table = $tmp;
 | 
						|
    }
 | 
						|
    // check that data isn't an empty array
 | 
						|
    if (empty($data)) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    // make sure we have fields to insert
 | 
						|
    $fields = array_keys(reset($data));
 | 
						|
    if (empty($fields)) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    $sql = 'INSERT INTO `'.$table.'` (`'.implode('`,`', $fields).'`)  VALUES ';
 | 
						|
    $values ='';
 | 
						|
 | 
						|
    foreach ($data as $row) {
 | 
						|
        if ($values != '') {
 | 
						|
            $values .= ',';
 | 
						|
        }
 | 
						|
        $rowvalues='';
 | 
						|
        foreach ($row as $key => $value) {
 | 
						|
            if ($rowvalues != '') {
 | 
						|
                $rowvalues .= ',';
 | 
						|
            }
 | 
						|
            if (is_null($value)) {
 | 
						|
                $rowvalues .= 'NULL';
 | 
						|
            } else {
 | 
						|
                $rowvalues .= "'" . mres($value) . "'";
 | 
						|
            }
 | 
						|
        }
 | 
						|
        $values .= "(".$rowvalues.")";
 | 
						|
    }
 | 
						|
 | 
						|
    $result = dbQuery($sql.$values);
 | 
						|
 | 
						|
    recordDbStatistic('insert', $time_start);
 | 
						|
    return $result;
 | 
						|
}//end dbBulkInsert()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Passed an array, table name, WHERE clause, and placeholder parameters, it attempts to update a record.
 | 
						|
 * Returns the number of affected rows
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbUpdate($data, $table, $where = null, $parameters = array())
 | 
						|
{
 | 
						|
    global $fullSql, $database_link;
 | 
						|
    $time_start = microtime(true);
 | 
						|
 | 
						|
    // the following block swaps the parameters if they were given in the wrong order.
 | 
						|
    // it allows the method to work for those that would rather it (or expect it to)
 | 
						|
    // follow closer with SQL convention:
 | 
						|
    // update the TABLE with this DATA
 | 
						|
    if (is_string($data) && is_array($table)) {
 | 
						|
        $tmp   = $data;
 | 
						|
        $data  = $table;
 | 
						|
        $table = $tmp;
 | 
						|
        // trigger_error('QDB - The first two parameters passed to update() were in reverse order, but it has been allowed', E_USER_NOTICE);
 | 
						|
    }
 | 
						|
 | 
						|
    // need field name and placeholder value
 | 
						|
    // but how merge these field placeholders with actual $parameters array for the WHERE clause
 | 
						|
    $sql = 'UPDATE `'.$table.'` set ';
 | 
						|
    foreach ($data as $key => $value) {
 | 
						|
        $sql .= '`'.$key.'` '.'=:'.$key.',';
 | 
						|
    }
 | 
						|
 | 
						|
    $sql = substr($sql, 0, -1);
 | 
						|
    // strip off last comma
 | 
						|
    if ($where) {
 | 
						|
        $sql .= ' WHERE '.$where;
 | 
						|
        $data = array_merge($data, $parameters);
 | 
						|
    }
 | 
						|
 | 
						|
    if (dbQuery($sql, $data)) {
 | 
						|
        $return = mysqli_affected_rows($database_link);
 | 
						|
    } else {
 | 
						|
        // echo("$fullSql");
 | 
						|
        //trigger_error('QDB - Update failed.', E_USER_WARNING);
 | 
						|
        $return = false;
 | 
						|
    }
 | 
						|
 | 
						|
    recordDbStatistic('update', $time_start);
 | 
						|
    return $return;
 | 
						|
}//end dbUpdate()
 | 
						|
 | 
						|
 | 
						|
function dbDelete($table, $where = null, $parameters = array())
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    $time_start = microtime(true);
 | 
						|
 | 
						|
    $sql = 'DELETE FROM `'.$table.'`';
 | 
						|
    if ($where) {
 | 
						|
        $sql .= ' WHERE '.$where;
 | 
						|
    }
 | 
						|
 | 
						|
    $result = dbQuery($sql, $parameters);
 | 
						|
 | 
						|
    recordDbStatistic('delete', $time_start);
 | 
						|
    if ($result) {
 | 
						|
        return mysqli_affected_rows($database_link);
 | 
						|
    } else {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
}//end dbDelete()
 | 
						|
 | 
						|
 | 
						|
/**
 | 
						|
 * Delete orphaned entries from a table that no longer have a parent in parent_table
 | 
						|
 * Format of parents array is as follows table.table_key_column<.target_key_column>
 | 
						|
 *
 | 
						|
 * @param string $target_table The table to delete entries from
 | 
						|
 * @param array $parents an array of parent tables to check.
 | 
						|
 * @return bool|int
 | 
						|
 */
 | 
						|
function dbDeleteOrphans($target_table, $parents)
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    $time_start = microtime(true);
 | 
						|
 | 
						|
    if (empty($parents)) {
 | 
						|
        // don't delete all entries if parents is missing
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    $target_table = mres($target_table);
 | 
						|
    $sql = "DELETE T FROM `$target_table` T";
 | 
						|
    $where = array();
 | 
						|
 | 
						|
    foreach ((array)$parents as $parent) {
 | 
						|
        $parent_parts = explode('.', mres($parent));
 | 
						|
        if (count($parent_parts) == 2) {
 | 
						|
            list($parent_table, $parent_column) = $parent_parts;
 | 
						|
            $target_column = $parent_column;
 | 
						|
        } elseif (count($parent_parts) == 3) {
 | 
						|
            list($parent_table, $parent_column, $target_column) = $parent_parts;
 | 
						|
        } else {
 | 
						|
            // invalid input
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        $sql .= " LEFT JOIN `$parent_table` ON `$parent_table`.`$parent_column` = T.`$target_column`";
 | 
						|
        $where[] = " `$parent_table`.`$parent_column` IS NULL";
 | 
						|
    }
 | 
						|
 | 
						|
    $query = "$sql WHERE" . implode(' AND', $where);
 | 
						|
    $result = dbQuery($query, array());
 | 
						|
 | 
						|
    recordDbStatistic('delete', $time_start);
 | 
						|
    if ($result) {
 | 
						|
        return mysqli_affected_rows($database_link);
 | 
						|
    } else {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Fetches all of the rows (associatively) from the last performed query.
 | 
						|
 * Most other retrieval functions build off this
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbFetchRows($sql, $parameters = array())
 | 
						|
{
 | 
						|
    $time_start = microtime(true);
 | 
						|
    $result = dbQuery($sql, $parameters);
 | 
						|
 | 
						|
    if ($result !== false) {
 | 
						|
        if (mysqli_num_rows($result) > 0) {
 | 
						|
            $rows = [];
 | 
						|
            while ($row = mysqli_fetch_assoc($result)) {
 | 
						|
                $rows[] = $row;
 | 
						|
            }
 | 
						|
 | 
						|
            mysqli_free_result($result);
 | 
						|
 | 
						|
            recordDbStatistic('fetchrows', $time_start);
 | 
						|
            return $rows;
 | 
						|
        }
 | 
						|
 | 
						|
        mysqli_free_result($result);
 | 
						|
    }
 | 
						|
 | 
						|
    // no records, thus return empty array
 | 
						|
    // which should evaluate to false, and will prevent foreach notices/warnings
 | 
						|
    recordDbStatistic('fetchrows', $time_start);
 | 
						|
    return [];
 | 
						|
}//end dbFetchRows()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * This is intended to be the method used for large result sets.
 | 
						|
 * It is intended to return an iterator, and act upon buffered data.
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbFetch($sql, $parameters = array())
 | 
						|
{
 | 
						|
    return dbFetchRows($sql, $parameters);
 | 
						|
    /*
 | 
						|
        // for now, don't do the iterator thing
 | 
						|
        $result = dbQuery($sql, $parameters);
 | 
						|
        if($result) {
 | 
						|
        // return new iterator
 | 
						|
        return new dbIterator($result);
 | 
						|
        } else {
 | 
						|
        return null; // ??
 | 
						|
        }
 | 
						|
     */
 | 
						|
}//end dbFetch()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Like fetch(), accepts any number of arguments
 | 
						|
 * The first argument is an sprintf-ready query stringTypes
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbFetchRow($sql = null, $parameters = array())
 | 
						|
{
 | 
						|
    $time_start = microtime(true);
 | 
						|
    $result         = dbQuery($sql, $parameters);
 | 
						|
    if ($result) {
 | 
						|
        $row = mysqli_fetch_assoc($result);
 | 
						|
        mysqli_free_result($result);
 | 
						|
 | 
						|
        recordDbStatistic('fetchrow', $time_start);
 | 
						|
        return $row;
 | 
						|
    } else {
 | 
						|
        return null;
 | 
						|
    }
 | 
						|
}//end dbFetchRow()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Fetches the first call from the first row returned by the query
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbFetchCell($sql, $parameters = array())
 | 
						|
{
 | 
						|
    $time_start = microtime(true);
 | 
						|
    $row = dbFetchRow($sql, $parameters);
 | 
						|
 | 
						|
    recordDbStatistic('fetchcell', $time_start);
 | 
						|
    if ($row) {
 | 
						|
        return array_shift($row);
 | 
						|
        // shift first field off first row
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
}//end dbFetchCell()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * This method is quite different from fetchCell(), actually
 | 
						|
 * It fetches one cell from each row and places all the values in 1 array
 | 
						|
 * */
 | 
						|
 | 
						|
 | 
						|
function dbFetchColumn($sql, $parameters = array())
 | 
						|
{
 | 
						|
    $time_start = microtime(true);
 | 
						|
    $cells          = array();
 | 
						|
    foreach (dbFetch($sql, $parameters) as $row) {
 | 
						|
        $cells[] = array_shift($row);
 | 
						|
    }
 | 
						|
 | 
						|
    recordDbStatistic('fetchcolumn', $time_start);
 | 
						|
    return $cells;
 | 
						|
}//end dbFetchColumn()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Should be passed a query that fetches two fields
 | 
						|
 * The first will become the array key
 | 
						|
 * The second the key's value
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
function dbFetchKeyValue($sql, $parameters = array())
 | 
						|
{
 | 
						|
    $data = array();
 | 
						|
    foreach (dbFetch($sql, $parameters) as $row) {
 | 
						|
        $key = array_shift($row);
 | 
						|
        if (sizeof($row) == 1) {
 | 
						|
            // if there were only 2 fields in the result
 | 
						|
            // use the second for the value
 | 
						|
            $data[$key] = array_shift($row);
 | 
						|
        } else {
 | 
						|
            // if more than 2 fields were fetched
 | 
						|
            // use the array of the rest as the value
 | 
						|
            $data[$key] = $row;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return $data;
 | 
						|
}//end dbFetchKeyValue()
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * This combines a query and parameter array into a final query string for execution
 | 
						|
 * PDO drivers don't need to use this
 | 
						|
 */
 | 
						|
 | 
						|
 | 
						|
function dbMakeQuery($sql, $parameters)
 | 
						|
{
 | 
						|
    // bypass extra logic if we have no parameters
 | 
						|
    if (sizeof($parameters) == 0) {
 | 
						|
        return $sql;
 | 
						|
    }
 | 
						|
 | 
						|
    $parameters = dbPrepareData($parameters);
 | 
						|
    // separate the two types of parameters for easier handling
 | 
						|
    $questionParams = array();
 | 
						|
    $namedParams    = array();
 | 
						|
    foreach ($parameters as $key => $value) {
 | 
						|
        if (is_numeric($key)) {
 | 
						|
            $questionParams[] = $value;
 | 
						|
        } else {
 | 
						|
            $namedParams[':'.$key] = $value;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    // sort namedParams in reverse to stop substring squashing
 | 
						|
    krsort($namedParams);
 | 
						|
 | 
						|
    // split on question-mark and named placeholders
 | 
						|
    if (preg_match('/(\[\[:[\w]+:\]\])/', $sql)) {
 | 
						|
        $result = preg_split('/(\?[a-zA-Z0-9_-]*)/', $sql, -1, (PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
 | 
						|
    } else {
 | 
						|
        $result = preg_split('/(\?|:[a-zA-Z0-9_-]+)/', $sql, -1, (PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE));
 | 
						|
    }
 | 
						|
 | 
						|
    // every-other item in $result will be the placeholder that was found
 | 
						|
    $query            = '';
 | 
						|
    $res_size = sizeof($result);
 | 
						|
    for ($i = 0; $i < $res_size; $i += 2) {
 | 
						|
        $query .= $result[$i];
 | 
						|
 | 
						|
        $j = ($i + 1);
 | 
						|
        if (array_key_exists($j, $result)) {
 | 
						|
            $test = $result[$j];
 | 
						|
            if ($test == '?') {
 | 
						|
                $query .= array_shift($questionParams);
 | 
						|
            } else {
 | 
						|
                $query .= $namedParams[$test];
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return $query;
 | 
						|
}//end dbMakeQuery()
 | 
						|
 | 
						|
 | 
						|
function dbPrepareData($data)
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    $values = array();
 | 
						|
 | 
						|
    foreach ($data as $key => $value) {
 | 
						|
        $escape = true;
 | 
						|
        // don't quote or esc if value is an array, we treat it
 | 
						|
        // as a "decorator" that tells us not to escape the
 | 
						|
        // value contained in the array
 | 
						|
        if (is_array($value) && !is_object($value)) {
 | 
						|
            $escape = false;
 | 
						|
            $value  = array_shift($value);
 | 
						|
        }
 | 
						|
 | 
						|
        // it's not right to worry about invalid fields in this method because we may be operating on fields
 | 
						|
        // that are aliases, or part of other tables through joins
 | 
						|
        // if(!in_array($key, $columns)) // skip invalid fields
 | 
						|
        // continue;
 | 
						|
        if ($escape) {
 | 
						|
            $values[$key] = "'".mysqli_real_escape_string($database_link, $value)."'";
 | 
						|
        } else {
 | 
						|
            $values[$key] = $value;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return $values;
 | 
						|
}//end dbPrepareData()
 | 
						|
 | 
						|
/**
 | 
						|
 * Given a data array, this returns an array of placeholders
 | 
						|
 * These may be question marks, or ":email" type
 | 
						|
 *
 | 
						|
 * @param array $values
 | 
						|
 * @return array
 | 
						|
 */
 | 
						|
function dbPlaceHolders($values)
 | 
						|
{
 | 
						|
    $data = array();
 | 
						|
    foreach ($values as $key => $value) {
 | 
						|
        if (is_numeric($key)) {
 | 
						|
            $data[] = '?';
 | 
						|
        } else {
 | 
						|
            $data[] = ':'.$key;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return $data;
 | 
						|
}//end dbPlaceHolders()
 | 
						|
 | 
						|
 | 
						|
function dbBeginTransaction()
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    mysqli_query($database_link, 'begin');
 | 
						|
}//end dbBeginTransaction()
 | 
						|
 | 
						|
 | 
						|
function dbCommitTransaction()
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    mysqli_query($database_link, 'commit');
 | 
						|
}//end dbCommitTransaction()
 | 
						|
 | 
						|
 | 
						|
function dbRollbackTransaction()
 | 
						|
{
 | 
						|
    global $database_link;
 | 
						|
    mysqli_query($database_link, 'rollback');
 | 
						|
}//end dbRollbackTransaction()
 | 
						|
 | 
						|
/**
 | 
						|
 * Generate a string of placeholders to pass to fill in a list
 | 
						|
 * result will look like this: (?, ?, ?, ?)
 | 
						|
 *
 | 
						|
 * @param $count
 | 
						|
 * @return string placholder list
 | 
						|
 */
 | 
						|
function dbGenPlaceholders($count)
 | 
						|
{
 | 
						|
    return '(' . implode(',', array_fill(0, $count, '?')) . ')';
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Update statistics for db operations
 | 
						|
 *
 | 
						|
 * @param string $stat fetchcell, fetchrow, fetchrows, fetchcolumn, update, insert, delete
 | 
						|
 * @param float $start_time The time the operation started with 'microtime(true)'
 | 
						|
 * @return float  The calculated run time
 | 
						|
 */
 | 
						|
function recordDbStatistic($stat, $start_time)
 | 
						|
{
 | 
						|
    global $db_stats, $db_stats_last;
 | 
						|
 | 
						|
    if (!isset($db_stats)) {
 | 
						|
        $db_stats = array(
 | 
						|
            'ops' => array(
 | 
						|
                'insert' => 0,
 | 
						|
                'update' => 0,
 | 
						|
                'delete' => 0,
 | 
						|
                'fetchcell' => 0,
 | 
						|
                'fetchcolumn' => 0,
 | 
						|
                'fetchrow' => 0,
 | 
						|
                'fetchrows' => 0,
 | 
						|
            ),
 | 
						|
            'time' => array(
 | 
						|
                'insert' => 0.0,
 | 
						|
                'update' => 0.0,
 | 
						|
                'delete' => 0.0,
 | 
						|
                'fetchcell' => 0.0,
 | 
						|
                'fetchcolumn' => 0.0,
 | 
						|
                'fetchrow' => 0.0,
 | 
						|
                'fetchrows' => 0.0,
 | 
						|
            ),
 | 
						|
        );
 | 
						|
        $db_stats_last = $db_stats;
 | 
						|
    }
 | 
						|
 | 
						|
    $runtime = microtime(true) - $start_time;
 | 
						|
    $db_stats['ops'][$stat]++;
 | 
						|
    $db_stats['time'][$stat] += $runtime;
 | 
						|
 | 
						|
    //double accounting corrections
 | 
						|
    if ($stat == 'fetchcolumn') {
 | 
						|
        $db_stats['ops']['fetchrows']--;
 | 
						|
        $db_stats['time']['fetchrows'] -= $runtime;
 | 
						|
    }
 | 
						|
    if ($stat == 'fetchcell') {
 | 
						|
        $db_stats['ops']['fetchrow']--;
 | 
						|
        $db_stats['time']['fetchrow'] -= $runtime;
 | 
						|
    }
 | 
						|
 | 
						|
    return $runtime;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Synchronize a relationship to a list of related ids
 | 
						|
 *
 | 
						|
 * @param string $table
 | 
						|
 * @param string $target_column column name for the target
 | 
						|
 * @param int $target column target id
 | 
						|
 * @param string $list_column related column names
 | 
						|
 * @param array $list list of related ids
 | 
						|
 * @return array [$inserted, $deleted]
 | 
						|
 */
 | 
						|
function dbSyncRelationship($table, $target_column = null, $target = null, $list_column = null, $list = null)
 | 
						|
{
 | 
						|
    $inserted = 0;
 | 
						|
 | 
						|
    $delete_query = "`$target_column`=? AND `$list_column`";
 | 
						|
    $delete_params = [$target];
 | 
						|
    if (!empty($list)) {
 | 
						|
        $delete_query .= ' NOT IN ' . dbGenPlaceholders(count($list));
 | 
						|
        $delete_params = array_merge($delete_params, $list);
 | 
						|
    }
 | 
						|
    $deleted = (int)dbDelete($table, $delete_query, $delete_params);
 | 
						|
 | 
						|
    $db_list = dbFetchColumn("SELECT `$list_column` FROM `$table` WHERE `$target_column`=?", [$target]);
 | 
						|
    foreach ($list as $item) {
 | 
						|
        if (!in_array($item, $db_list)) {
 | 
						|
            dbInsert([$target_column => $target, $list_column => $item], $table);
 | 
						|
            $inserted++;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return [$inserted, $deleted];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Synchronize a relationship to a list of relations
 | 
						|
 *
 | 
						|
 * @param string $table
 | 
						|
 * @param array $relationships array of relationship pairs with columns as keys and ids as values
 | 
						|
 * @return array [$inserted, $deleted]
 | 
						|
 */
 | 
						|
function dbSyncRelationships($table, $relationships = array())
 | 
						|
{
 | 
						|
    $changed = [[0, 0]];
 | 
						|
    list($target_column, $list_column) = array_keys(reset($relationships));
 | 
						|
 | 
						|
    $grouped = [];
 | 
						|
    foreach ($relationships as $relationship) {
 | 
						|
        $grouped[$relationship[$target_column]][] = $relationship[$list_column];
 | 
						|
    }
 | 
						|
 | 
						|
    foreach ($grouped as $target => $list) {
 | 
						|
        $changed[] = dbSyncRelationship($table, $target_column, $target, $list_column, $list);
 | 
						|
    }
 | 
						|
 | 
						|
    return [array_sum(array_column($changed, 0)), array_sum(array_column($changed, 1))];
 | 
						|
}
 |