* Copyright (C) 2010 - 2025 * All Rights Reserved. * * Contributor(s): * Mark J Crane * Luis Daniel Lucio Quiroz */ /** * Database class * * @property $name Alias of app_name */ class database { const TABLE_PREFIX = 'v_'; /** * Stores the application built from the app_config files. * * @see $apps * @access private * @var array */ private static $apps = []; /** * Singleton type class * * @var database */ private static $database; /** * Database connection * * @access private * @var PDO object */ public $db; /** * Driver to use. * * @access public * @var string Can be pgsql, mysql, sqlite, odbc */ public $driver; /** * Alias of driver. * * @access public * @see $driver * @var string Can be pgsql, mysql, sqlite, odbc */ public $type; /** * Host for database connection * * @access public * @var string host name or IP address. */ public $host; /** * Port number * * @access public * @var int 1025 - 65534 */ public $port; /** * Database name * * @access public * @var string */ public $db_name; /** * Database security * * @access public * @var boolean */ public $db_secure; /** * Specifies the file name of the client SSL certificate * * @access public * @var string full path */ public $db_cert_authority; /** * Username used to connect * * @access public * @var string */ public $username; /** * Password used to connect * * @access public * @var string */ public $password; /** * Full path to file name. * * @access public * @var string full path to file name */ public $path; /** * Where clause(s) of an SQL statement. *

Array of arrays must be passed with each having the * following keys: *

  1. 'name' - Any valid column name.
  2. *
  3. 'operator' - Must be one of the following values: =, >, <, >=, <=, <>, !=
  4. *
  5. 'value' - Value being matched

*

Example Usage:

*

$db->where['SearchTerm'] = ['name'=>'MyColumn','operator'=>'=','value'=>'MySearchTerm'

*

$db->where['NextSearchTerm'] = * ['name'=>'MyColumn','operator'=>'=','value'=>'MyOtherSearchTerm'

*

Below is equivalent to the above.

*

$db->where[0] = ['name'=>'MyColumn','operator'=>'=','value'=>'MyValue'

*

$db->where[1] = ['name'=>'MyColumn','operator'=>'=>','value'=>'MyValue'

* * @access public * @see $order_by * @var array Two dimensional array of key value pairs */ public $where; // array /** * Order By clause(s) of an SQL statement. *

Array of arrays must be passed with each having the * following keys: *

  1. 'name' - Any valid column name.
  2. *
  3. 'operator' - Must be one of the following values: =, >, <, >=, <=, <>, !=
  4. *
  5. 'value' - Value being matched

*

Example Usage:

*

$db->where['SearchTerm'] = ['name'=>'MyColumn','operator'=>'=','value'=>'MySearchTerm'

*

$db->where['NextSearchTerm'] = * ['name'=>'MyColumn','operator'=>'=','value'=>'MyOtherSearchTerm'

*

Below is equivalent to the above.

*

$db->where[0] = ['name'=>'MyColumn','operator'=>'=','value'=>'MyValue'

*

$db->where[1] = ['name'=>'MyColumn','operator'=>'=>','value'=>'MyValue'

* * @access private * @see $where * @var array Two dimensional array of key value pairs */ public $order_by; /** * Ascending or Descending order. * * @var string * @access public */ public $order_type; /** * Numerical value to limit returned results. * * @var int Used for 'LIMIT' in SQL statement. * @access public */ public $limit; /** * Numerical value to offset returned results. * * @var int Used for 'OFFSET' in SQL statement. * @access public */ public $offset; /** *

Array of fields.

*

Fields are specified in 'name'=>'value' format. *

Used by {@link database::add() } and {@link database::update() }

* * @access public * @see database::add() * @see database::update() * @var array Array of columns */ public $fields; /** * Unknown property * * @var unknown * @access public */ public $count; /** * Unknown property * * @var unknown * @access public */ public $sql; /** * Stores the application name making the request. * * @var string App name making database request. * @access public */ public $name; /** * Stores the application name making the request. * * @see $app_uuid * @access public * @var string App name making database request. */ public $app_name; /** * Stores the application UUID making the request. * * @see $app_name * @access public * @var string */ public $app_uuid; /** *

Stores the domain UUID making the request.

*

This is defaulted to the Session domain UUID.

* * @access public * @uses $this->domain_uuid
Default value upon object creation * @var string Domain UUID making request. */ public $domain_uuid; /** *

Stores the user UUID making the request.

*

This is defaulted to the Session domain UUID.

* * @access public * @uses $this->user_uuid
Default value upon object creation * @var string Domain UUID making request. */ public $user_uuid; /** *

Message for the query results.

* * @var array Contains the message array after a query * @access private */ public $message; /** * SSL Mode used to connect to the database * * @var string prefer or verify-ca. Default is 'prefer' */ public $ssl_mode; /** * Table name. * * @access private * @var string sanitized */ private $table; /** *

Stores the result from the most recent query. The type will be based on what was requested.

*

NOTE: If an error occurred on the last query the result is set to an empty string.

* * @var mixed */ private $result; /** * Config object used to get the database connection params * * @var config */ private $config; /** * Constructor for the class. * * This method initializes the object with setting_array and session data. * * @param array $params An optional array of settings to override default values. Defaults to []. */ public function __construct(array $params = []) { // handle the config object if (isset($params['config'])) { $config = $params['config']; } else { // use singleton config $config = config::load(); } // driver and type point to the same value $this->driver = $config->get('database.0.type', 'pgsql'); $this->type = $config->get('database.0.type', 'pgsql'); $this->host = $config->get('database.0.host', '127.0.0.1'); $this->port = $config->get('database.0.port', '5432'); $this->username = $config->get('database.0.username', 'fusionpbx'); $this->password = $config->get('database.0.password', 'fusionpbx'); $this->db_name = $config->get('database.0.name', 'fusionpbx'); $this->db_secure = $config->get('database.0.secure', ''); $this->db_cert_authority = $config->get('database.0.cert_authority', ''); $this->ssl_mode = $config->get('database.0.ssl_mode', ''); // save the reference to the single instance of the config to this object $this->config = $config; // connect to the database now $this->connect(); // set the user_uuid if (!empty($params['user_uuid'])) { // use the parameter as the first priority when available $this->user_uuid = $params['user_uuid']; } elseif (!empty($_SESSION['user_uuid'])) { // use the session when available $this->user_uuid = $_SESSION['user_uuid']; } // set the domain_uuid if (!empty($params['domain_uuid'])) { // use the parameter as the first priority when available $this->domain_uuid = $params['domain_uuid']; } elseif (!empty($_SESSION['domain_uuid'])) { // use the session when available $this->domain_uuid = $_SESSION['domain_uuid']; } } /** *

Connect to the database.

*

Database driver must be set before calling connect.

*

For types other than sqlite. Execution will stop on failure.

* * @depends database::driver Alias of database::type. */ public function connect() { // get the database connection settings // $db_type = $conf['database.0.type']; // $db_host = $conf['database.0.host']; // $db_port = $conf['database.0.port']; // $db_name = $conf['database.0.name']; // $db_username = $conf['database.0.username']; // $db_password = $conf['database.0.password']; // debug info // echo "db type:".$db_type."\n"; // echo "db host:".$db_host."\n"; // echo "db port:".$db_port."\n"; // echo "db name:".$db_name."\n"; // echo "db username:".$db_username."\n"; // echo "db password:".$db_password."\n"; // echo "db path:".$db_path."\n"; // echo "\n"; // set defaults if (!isset($this->driver) && isset($db_type)) { $this->driver = $db_type; } if (!isset($this->type) && isset($db_type)) { $this->type = $db_type; } if (!isset($this->host) && isset($db_host)) { $this->host = $db_host; } if (!isset($this->port) && isset($db_port)) { $this->port = $db_port; } if (!isset($this->db_name) && isset($db_name)) { $this->db_name = $db_name; } if (!isset($this->db_secure) && isset($db_secure)) { $this->db_secure = $db_secure; } else { $this->db_secure = false; } if (!isset($this->username) && isset($db_username)) { $this->username = $db_username; } if (!isset($this->password) && isset($db_password)) { $this->password = $db_password; } if (!isset($this->path) && isset($db_path)) { $this->path = $db_path; } if ($this->driver == 'sqlite') { if (empty($this->db_name)) { $server_name = $_SERVER['SERVER_NAME']; $server_name = str_replace('www.', '', $server_name); $db_name_short = $server_name; $this->db_name = $server_name . '.db'; } else { $db_name_short = $this->db_name; } $this->path = realpath($this->path); if (file_exists($this->path . '/' . $this->db_name)) { // connect to the database $this->db = new PDO('sqlite:' . $this->path . '/' . $this->db_name); // sqlite 3 // PRAGMA commands $this->db->query('PRAGMA foreign_keys = ON;'); $this->db->query('PRAGMA journal_mode = wal;'); // add additional functions to SQLite so that they are accessible inside SQL // bool PDO::sqliteCreateFunction ( string function_name, callback callback [, int num_args] ) $this->db->sqliteCreateFunction('md5', 'php_md5', 1); $this->db->sqliteCreateFunction('unix_timestamp', 'php_unix_timestamp', 1); $this->db->sqliteCreateFunction('now', 'php_now', 0); $this->db->sqliteCreateFunction('sqlitedatatype', 'php_sqlite_data_type', 2); $this->db->sqliteCreateFunction('strleft', 'php_left', 2); $this->db->sqliteCreateFunction('strright', 'php_right', 2); } else { $error_message = 'file not found'; $message['message'] = $error_message; $this->message = $message; return false; } } if ($this->driver == 'mysql') { try { // mysql pdo connection if (strlen($this->host) == 0 && empty($this->port)) { // if both host and port are empty use the unix socket $this->db = new PDO("mysql:host=$this->host;unix_socket=/var/run/mysqld/mysqld.sock;dbname=$this->db_name", $this->username, $this->password); } else { if (empty($this->port)) { // leave out port if it is empty $this->db = new PDO("mysql:host=$this->host;dbname=$this->db_name;", $this->username, $this->password, [ PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION, ]); } else { $this->db = new PDO("mysql:host=$this->host;port=$this->port;dbname=$this->db_name;", $this->username, $this->password, [ PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION, ]); } } } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } if ($this->driver == 'pgsql') { // database connection try { if (!empty($this->host)) { if (empty($this->port)) { $this->port = '5432'; } if ($this->db_secure === true) { $this->db = new PDO("pgsql:host=$this->host port=$this->port dbname=$this->db_name user=$this->username password=$this->password sslmode=$this->ssl_mode sslrootcert=$this->db_cert_authority"); } else { $this->db = new PDO("pgsql:host=$this->host port=$this->port dbname=$this->db_name user=$this->username password=$this->password"); } } else { $this->db = new PDO("pgsql:dbname=$this->db_name user=$this->username password=$this->password"); } } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } if ($this->driver == 'odbc') { // database connection try { $this->db = new PDO('odbc:' . $this->db_name, $this->username, $this->password); } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } // connected to the database return true; } /** * Returns the depth of an array * * @param array $array Reference to array * * @return int Depth of array * @internal Moved to class to conserve resources. */ public static function array_depth(array &$array) { $depth = 0; if (is_array($array)) { $depth++; foreach ($array as $value) { if (is_array($value)) { $depth = self::array_depth($value) + 1; } } } return $depth; } /** * Searches through all fields to see if domain_uuid exists * * @param string $name * * @return boolean true on success and false on failure * @see database::get_apps() * @uses self::$apps directly */ public static function domain_uuid_exists($name) { // get the $apps array from the installed apps from the core and mod directories if (count(self::$apps) == 0) { self::get_apps(); } // search through all fields to see if domain_uuid exists foreach (self::$apps as $x => &$app) { if (is_array($app['db'])) { foreach ($app['db'] as $y => $row) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } if ($table_name === self::TABLE_PREFIX . $name) { if (is_array($row['fields'])) { foreach ($row['fields'] as $field) { if ($field['name'] == 'domain_uuid') { return true; } } // foreach } // is array } } // foreach } // is array } // foreach // not found return false; } /** * Returns a new connected database object.
*

This allows a shortcut for a common syntax. For more information * on how the connection happens see {@link database::__construct()} and * {@link database::connect()}

*

Usage:
*   $database_object = database::new();

* * @return database the new instance of the database object already connected * @see database::__construct() * @see database::connect() */ public static function new(array $params = []) { // re-use the database connection if (self::$database === null) { self::$database = new database($params); if (!self::$database->is_connected()) { self::$database->connect(); } } // set the user_uuid if (!empty($params['user_uuid'])) { // use the parameter as the first priority when available self::$database->user_uuid = $params['user_uuid']; } elseif (!empty($_SESSION['user_uuid'])) { // use the session when available self::$database->user_uuid = $_SESSION['user_uuid']; } // set the domain_uuid if (!empty($params['domain_uuid'])) { // use the parameter as the first priority when available self::$database->domain_uuid = $params['domain_uuid']; } elseif (!empty($_SESSION['domain_uuid'])) { // use the session when available self::$database->domain_uuid = $_SESSION['domain_uuid']; } return self::$database; } /** * Ensure the database is still connected and active. *

NOTE:
* There is no method in PDO that can reliably detect if the connection is active. Therefore, a lightweight * query is executed using the statement select 1.

* * @return bool True if the database is connected. False otherwise. */ public function is_connected(): bool { try { $stmt = false; if ($this->db !== null) $stmt = $this->db->query('SELECT 1'); return $stmt !== false; } catch (PDOException $ex) { // database is not connected return false; } catch (Exception $e) { // some other error has occurred, so record it $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } return true; } /** * Magic function called whenever a property is requested. *

If any case statement is removed then access to the variable will be removed.

* * @param mixed $name object property * * @return mixed */ public function __get($name) { // remove any case statement below to remove access to the variable switch ($name) { case 'name': return $this->app_name; case 'app_name': case 'app_uuid': case 'db': case 'db_cert_authority': case 'db_name': case 'db_secure': case 'domain_uuid': case 'driver': case 'fields': case 'host': case 'limit': case 'message': case 'offset': case 'order_by': case 'order_type': case 'password': case 'path': case 'port': case 'result': case 'sql': case 'table': case 'type': case 'username': case 'where': case 'debug': return $this->{$name}; case 'count': return $this->count(); default: trigger_error('Object property not available', E_USER_ERROR); } } /** *

Magic function called whenever a property is attempted to be set.

*

This is used to protect the values stored in the object properties.

* * @param mixed $name Name of object property * @param mixed $value Value of property */ public function __set($name, $value) { switch ($name) { case 'name': case 'app_name': $this->app_name = self::sanitize($value); break; case 'message': if (is_array($value)) { $this->message = $value; } else { trigger_error('Message property must be set to array type', E_USER_ERROR); } break; case 'table': $this->table = self::sanitize($value); break; case 'db_name': $this->db_name = self::sanitize($value); break; case 'db': if ($name instanceof PDO) { $this->db = $value; } else { trigger_error('db property must be a PDO object!', E_USER_ERROR); } break; case 'count': break; case 'path': $value = realpath($value); if (file_exists($value)) { $this->path = $value; } else { trigger_error('Unable to find database path file!', E_USER_ERROR); } break; case 'db_cert_authority': if (!file_exists($value)) { trigger_error('db cert authority not found!', E_USER_WARNING); } $this->db_cert_authority = $value; break; case 'port': $value = (int) $value; // force cast to int if ($value > 1023 && $value < 65536) { $this->port = $value; } // valid values are 1024...65535 else { trigger_error('Port not a valid range', E_USER_ERROR); } break; case 'app_uuid': case 'domain_uuid': if (is_uuid($value)) { $this->domain_uuid = $value; } break; case 'type': case 'driver': switch ($value) { case 'pgsql': case 'mysql': case 'sqlite': case 'odbc': $this->type = $value; $this->driver = $value; break; default: trigger_error('Type/Driver must be set to pgsql,mysql,sqlite,odbc', E_USER_ERROR); break; } case 'offset': case 'limit': if (is_int($value)) { $this->$name = $value; } else { trigger_error('Offset or Limit not set to valid integer. Resetting to zero!', E_USER_WARNING); } break; case '': trigger_error('Database property must not be empty', E_USER_ERROR); break; case 'null': case null: trigger_error('Database property must not be null', E_USER_ERROR); break; case 'debug': $this->debug = $value; } } /** * Returns a sanitized string value safe for the database or table name. * * @param string $value To be sanitized * * @return string Sanitized using preg_replace('#[^a-zA-Z0-9_\-]#', '') * @see preg_replace() */ public static function sanitize(string $value) { return preg_replace('#[^a-zA-Z0-9_\-]#', '', $value); } /** * Counts the number of rows. * * @return int Represents the number of counted rows or -1 if failed. */ public function count() { // connect to the database if needed if (!$this->db) { $this->connect(); } // return if the table name is not set if (empty($this->table)) { return; } // sanitize the table name // $this->table = self::sanitize($this->table); // no longer needed // get the number of rows $sql = 'select count(*) as num_rows from ' . $this->table . ' '; $i = 0; if (is_array($this->where)) { foreach ($this->where as $row) { // sanitize the name $row['name'] = self::sanitize($row['name']); // validate the operator switch ($row['operator']) { case '<': break; case '>': break; case '<=': break; case '>=': break; case '=': break; case '<>': break; case '!=': break; default: // invalid operator return -1; } // build the sql if ($i == 0) { $sql .= 'where ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['name'] . ' '; } else { $sql .= 'and ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['name'] . ' '; } // add the name and value to the params array $params[$row['name']] = $row['value']; // increment $i $i++; } } // unset($this->where); //should not be objects resposibility $prep_statement = $this->db->prepare($sql); if ($prep_statement) { if (!isset($params)) { $params = null; } $prep_statement->execute($params); $row = $prep_statement->fetch(PDO::FETCH_ASSOC); if ($row['num_rows'] > 0) { return $row['num_rows']; } else { return 0; } } unset($prep_statement); } public function execute($sql, $parameters = null, $return_type = 'all') { // connect to the database if needed if (!$this->db) { $this->connect(); } // set the error mode $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // run the query, and return the results try { $prep_statement = $this->db->prepare($sql); if (is_array($parameters)) { $prep_statement->execute($parameters); } else { $prep_statement->execute(); } $message['message'] = 'OK'; $message['code'] = '200'; $message['sql'] = $sql; if (is_array($parameters)) { $message['parameters'] = $parameters; } $this->message = $message; // return the results switch ($return_type) { case 'all': return $prep_statement->fetchAll(PDO::FETCH_ASSOC); case 'row': return $prep_statement->fetch(PDO::FETCH_ASSOC); case 'column'; return $prep_statement->fetchColumn(); default: return $prep_statement->fetchAll(PDO::FETCH_ASSOC); } } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } // Use this function to run complex queries /** * Returns the config object used to create this database object * * @return config Config object */ public function config(): config { return $this->config; } /** * Returns the table names from the database. * * @return array tables * @depends connect() */ public function tables() { $result = []; // connect to the database if needed if (!$this->db) { $this->connect(); } if ($this->type == 'sqlite') { $sql = 'SELECT name FROM sqlite_master '; $sql .= "WHERE type='table' "; $sql .= 'order by name;'; } if ($this->type == 'pgsql') { $sql = 'select table_name as name '; $sql .= 'from information_schema.tables '; $sql .= "where table_schema='public' "; $sql .= "and table_type='BASE TABLE' "; $sql .= 'order by table_name '; } if ($this->type == 'mysql') { $sql = 'show tables'; } if ($this->type == 'mssql') { $sql = 'SELECT * FROM sys.Tables order by name asc'; } $prep_statement = $this->db->prepare($sql); $prep_statement->execute(); $tmp = $prep_statement->fetchAll(PDO::FETCH_NAMED); if ($this->type == 'pgsql' || $this->type == 'sqlite' || $this->type == 'mssql') { if (is_array($tmp)) { foreach ($tmp as $row) { $result[]['name'] = $row['name']; } } } if ($this->type == 'mysql') { if (is_array($tmp)) { foreach ($tmp as $row) { $table_array = array_values($row); $result[]['name'] = $table_array[0]; } } } return $result; } // delete /** * Checks if the table exists in the database. *

Note:
* Table name must be sanitized. Otherwise, a warning will be * emitted and false will be returned.

* * @param string $table_name Sanitized name of the table to search for. * * @return boolean Returns true if the table exists and false if it does not. * @depends connect() */ public function table_exists(string $table_name) { if (self::sanitize($table_name) != $table_name) { trigger_error('Table Name must be sanitized', E_USER_WARNING); return false; } // connect to the database if needed if (!$this->db) { $this->connect(); } // if unable to connect to the database if (!$this->db) { $message['message'] = 'Unable to connect to database'; $message['code'] = '500'; $message['line'] = __LINE__; $message['file'] = __FILE__; $message['trace'] = ''; $message['debug'] = debug_backtrace(); $this->message = $message; return false; } // query table store to see if the table exists $sql = ''; if ($this->type == 'sqlite') { $sql .= "SELECT * FROM sqlite_master WHERE type='table' and name='$table_name' "; } if ($this->type == 'pgsql') { $sql .= "select * from pg_tables where schemaname='public' and tablename = '$table_name' "; } if ($this->type == 'mysql') { $sql .= "SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = '" . $this->db_name . "' and TABLE_NAME = '$table_name' "; } $prep_statement = $this->db->prepare($sql); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); if (count($result) > 0) { return true; // table exists } else { return false; // table doesn't exist } } // count /** * Checks if the column exists in the database. *

Note:
* Tables and Column names must be sanitized. Otherwise, a warning will be * emitted and false will be returned.

* * @param string $table_name Sanitized name of the table to search for. * @param string $column_name Sanitized name of the column to search for. * * @return boolean Returns true if the column exists and false if it does not. * @depends connect() */ public function column_exists(string $table_name, string $column_name) { // sanitize the table name if (self::sanitize($table_name) != $table_name) { trigger_error('Table Name must be sanitized', E_USER_WARNING); return false; } // sanitize the column name if (self::sanitize($column_name) != $column_name) { trigger_error('Column Name must be sanitized', E_USER_WARNING); return false; } // connect to the database if needed if (!$this->db) { $this->connect(); } // if unable to connect to the database if (!$this->db) { $backtrace = debug_backtrace(); echo "Connection Failed
\n"; echo 'line number ' . __line__ . "
\n"; echo '
';
			print_r($backtrace);
			echo '
'; return false; } // check the sqlite database to see if the column exists // if ($this->db_type == "sqlite") { // $table_info = $this->table_info($table_name); // if ($this->sqlite_column_exists($table_info, $column_name)) { // return true; // } // else { // return false; // } // } // check the postgresql database to see if the column exists if ($this->type == 'pgsql') { $sql = "SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = '$table_name' limit 1) AND attname = '$column_name'; "; } // check the mysql database to see if the column exists if ($this->type == 'mysql') { // $sql .= "SELECT * FROM information_schema.COLUMNS where TABLE_SCHEMA = '$db_name' and TABLE_NAME = '$table_name' and COLUMN_NAME = '$column_name' "; $sql = "show columns from $table_name where field = '$column_name' "; } // return the results from the sql query if (empty($sql)) { return false; } else { $prep_statement = $this->db->prepare($sql); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); if (!$result) { return false; } if (count($result) > 0) { return true; } else { return false; } unset($prep_statement); } } // select /** * Queries {@link database::table_info()} to return the fields. * * @access public * @return array Two dimensional array * @depends table_info() */ public function fields() { // public $db; // public $type; // public $table; // public $name; // initialize the array $result = []; // get the table info $table_info = $this->table_info(); // set the list of fields if ($this->type == 'sqlite') { if (is_array($table_info)) { foreach ($table_info as $row) { $result[]['name'] = $row['name']; } } } if ($this->type == 'pgsql') { if (is_array($table_info)) { foreach ($table_info as $row) { $result[]['name'] = $row['column_name']; } } } if ($this->type == 'mysql') { if (is_array($table_info)) { foreach ($table_info as $row) { $result[]['name'] = $row['Field']; } } } if ($this->type == 'mssql') { if (is_array($table_info)) { foreach ($table_info as $row) { $result[]['name'] = $row['COLUMN_NAME']; } } } // return the result array return $result; } /** * Returns table information from the database. * * @return array table info * @depends connect() */ public function table_info() { // public $db; // public $type; // public $table; // public $name; // connect to the database if needed if (!$this->db) { $this->connect(); } // get the table info if (empty($this->table)) { return false; } if ($this->type == 'sqlite') { $sql = 'PRAGMA table_info(' . $this->table . ');'; } if ($this->type == 'pgsql') { $sql = 'SELECT ordinal_position, '; $sql .= 'column_name, '; $sql .= 'data_type, '; $sql .= 'column_default, '; $sql .= 'is_nullable, '; $sql .= 'character_maximum_length, '; $sql .= 'numeric_precision '; $sql .= 'FROM information_schema.columns '; $sql .= "WHERE table_name = '" . $this->table . "' "; $sql .= "and table_catalog = '" . $this->db_name . "' "; $sql .= 'ORDER BY ordinal_position; '; } if ($this->type == 'mysql') { $sql = 'DESCRIBE ' . $this->table . ';'; } if ($this->type == 'mssql') { $sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" . $this->table . "'"; } $prep_statement = $this->db->prepare($sql); $prep_statement->execute(); // set the result array return $prep_statement->fetchAll(PDO::FETCH_ASSOC); } /** * Searches database using the following object properties: *
    *
  1. table - sanitized name of the table {@see database::table}
  2. *
  3. where - where clause {@see database::where}
  4. *
  5. order_by - order_by clause {@see database::order_by}
  6. *
  7. limit - limit clause {@see database::limit}
  8. *
  9. offset - offset clause {@see database::offset}
  10. *
* * @return boolean * @depends connect() */ public function find() { // connect; // table; // where; // order_by; // limit; // offset; // connect to the database if needed if (!$this->db) { $this->connect(); } // get data from the database $sql = 'select * from ' . $this->table . ' '; if ($this->where) { $i = 0; if (is_array($this->where)) { foreach ($this->where as $row) { // sanitize the name $row['name'] = self::sanitize($row['name']); // validate the operator switch ($row['operator']) { case '<': break; case '>': break; case '<=': break; case '>=': break; case '=': break; case '<>': break; case '!=': break; default: // invalid operator return false; } // build the sql if ($i == 0) { // $sql .= 'where '.$row['name']." ".$row['operator']." '".$row['value']."' "; $sql .= 'where ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['name'] . ' '; } else { // $sql .= "and ".$row['name']." ".$row['operator']." '".$row['value']."' "; $sql .= 'and ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['name'] . ' '; } // add the name and value to the params array $params[$row['name']] = $row['value']; // increment $i $i++; } } } if (is_array($this->order_by)) { $sql .= 'order by '; $i = 1; if (is_array($this->order_by)) { foreach ($this->order_by as $row) { // sanitize the name $row['name'] = self::sanitize($row['name']); // sanitize the order switch ($row['order']) { case 'asc': break; case 'desc': break; default: $row['order'] = ''; } // build the sql if (count($this->order_by) == $i) { $sql .= $row['name'] . ' ' . $row['order'] . ' '; } else { $sql .= $row['name'] . ' ' . $row['order'] . ', '; } // increment $i $i++; } } } // limit if (isset($this->limit) && is_numeric($this->limit)) { $sql .= 'limit ' . $this->limit . ' '; } // offset if (isset($this->offset) && is_numeric($this->offset)) { $sql .= 'offset ' . $this->offset . ' '; } $prep_statement = $this->db->prepare($sql); if ($prep_statement) { $prep_statement->execute($params); $array = $prep_statement->fetchAll(PDO::FETCH_ASSOC); unset($prep_statement); return $array; } else { return false; } } // end function copy public function delete(array $array) { // set the default value $retval = true; // return the array if (!is_array($array)) { return false; } // connect to the database if needed if (!$this->db) { $this->connect(); } // set the message id $m = 0; // debug sql // $this->debug["sql"] = true; // set the message id $m = 0; // loop through the array $checked = false; $x = 0; foreach ($array as $parent_name => $tables) { if (is_array($tables)) { // get the application name and uuid if (class_exists($parent_name) && defined("$parent_name::app_name")) { $this->app_name = $parent_name::app_name; $this->app_uuid = $parent_name::app_uuid; } // process the array foreach ($tables as $id => $row) { // prepare the variables $parent_name = self::sanitize($parent_name); $parent_key_name = self::singular($parent_name) . '_uuid'; // build the delete array if (!empty($row['checked']) && $row['checked'] == 'true') { // set checked to true $checked = true; // delete the child data if (isset($row[$parent_key_name])) { $new_array[$parent_name][$x][$parent_key_name] = $row[$parent_key_name]; } // remove the row from the main array unset($array[$parent_name][$x]); } // loop through the fields foreach ($row as $field_name => $field_value) { // find the child tables $y = 0; if (is_array($field_value)) { // prepare the variables $child_name = self::sanitize($field_name); $child_key_name = self::singular($child_name) . '_uuid'; // loop through the child rows foreach ($field_value as $sub_row) { // build the delete array if ($row['checked'] == 'true') { // set checked to true $checked = true; // delete the child data $new_array[$child_name][][$child_key_name] = $sub_row[$child_key_name]; // remove the row from the main array unset($array[$parent_name][$x][$child_name][$y]); } // increment the value $y++; } } } // increment the value $x++; } } } // if not checked, then copy the array to the delete array if (!$checked) { $new_array = $array; } // get the current data if (count($new_array) > 0) { // build an array of tables, fields, and values foreach ($new_array as $table_name => $rows) { foreach ($rows as $row) { foreach ($row as $field_name => $field_value) { $keys[$table_name][$field_name][] = $field_value; } } } // use the array to get a copy of the parent data before deleting it foreach ($new_array as $table_name => $rows) { foreach ($rows as $row) { $table_name = self::sanitize($table_name); $sql = 'select * from ' . self::TABLE_PREFIX . $table_name . ' '; $i = 0; foreach ($row as $field_name => $field_value) { if ($i == 0) { $sql .= 'where '; } else { $sql .= 'and '; } $sql .= $field_name . ' in ( '; $i = 0; foreach ($keys[$table_name][$field_name] as $field_value) { $field_name = self::sanitize($field_name); if ($i > 0) { $sql .= ' ,'; } $sql .= ' :' . $field_name . '_' . $i . ' '; $i++; } $sql .= ') '; $i = 0; foreach ($keys[$table_name][$field_name] as $field_value) { $parameters[$field_name . '_' . $i] = $field_value; $i++; } } } if (isset($field_value) && $field_value != '') { $results = $this->execute($sql, $parameters, 'all'); unset($parameters); if (is_array($results)) { $old_array[$table_name] = $results; } } } // get relations array $relations = self::get_relations($parent_name); // add child data to the old array foreach ($old_array as $parent_name => $rows) { // get relations array $relations = self::get_relations($parent_name); // loop through the rows $x = 0; foreach ($rows as $row) { if (is_array($relations)) { foreach ($relations as $relation) { if ($relation['key']['action']['delete'] == 'cascade') { // set the child table $child_table = $relation['table']; // remove the v_ prefix if (substr($child_table, 0, strlen(self::TABLE_PREFIX)) == self::TABLE_PREFIX) { $child_table = substr($child_table, strlen(self::TABLE_PREFIX)); } // get the child data $sql = 'select * from ' . self::TABLE_PREFIX . $child_table . ' '; $sql .= 'where ' . $relation['field'] . ' = :' . $relation['field']; $parameters[$relation['field']] = $row[$relation['field']]; $results = $this->execute($sql, $parameters, 'all'); unset($parameters); if (is_array($results) && $parent_name !== $child_table) { $old_array[$parent_name][$x][$child_table] = $results; } // delete the child data if (isset($row[$relation['field']]) && !empty($row[$relation['field']])) { $sql = 'delete from ' . self::TABLE_PREFIX . $child_table . ' '; $sql .= 'where ' . $relation['field'] . ' = :' . $relation['field']; $parameters[$relation['field']] = $row[$relation['field']]; // $this->execute($sql, $parameters); } unset($parameters); } } } $x++; } } } // use a try catch around the transaction try { // start the atomic transaction $this->db->beginTransaction(); // delete the current data foreach ($new_array as $table_name => $rows) { // get the application name and uuid if (class_exists($table_name) && defined("$table_name::app_name")) { $this->app_name = $table_name::app_name; $this->app_uuid = $table_name::app_uuid; } if (empty($this->app_name)) { $app_name_singular = self::singular($table_name); if (class_exists($app_name_singular) && defined("$app_name_singular::app_name")) { $this->app_name = $app_name_singular::app_name; $this->app_uuid = $app_name_singular::app_uuid; } } // build and run the delete SQL statements foreach ($rows as $row) { if (permission_exists(self::singular($table_name) . '_delete')) { $sql = 'delete from ' . self::TABLE_PREFIX . $table_name . ' '; $i = 0; foreach ($row as $field_name => $field_value) { // echo "field: ".$field_name." = ".$field_value."\n"; if ($i == 0) { $sql .= 'where '; } else { $sql .= 'and '; } $sql .= $field_name . ' = :' . $field_name . ' '; $parameters[$field_name] = $field_value; $i++; } try { $this->execute($sql, $parameters); $message['message'] = 'OK'; $message['code'] = '200'; $message['uuid'] = $id; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; // $message["details"][$m]["uuid"] = $parent_key_value; $message['details'][$m]['sql'] = $sql; $this->message = $message; $m++; unset($sql, $statement); } catch (PDOException $e) { $retval = false; $message['message'] = 'Bad Request'; $message['code'] = '400'; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['sql'] = $sql; $this->message = $message; $m++; } unset($parameters); } // if permission } // foreach rows } // foreach $array // commit the atomic transaction $this->db->commit(); } catch (PDOException $e) { // rollback the transaction on error if ($this->db->inTransaction()) { $this->db->rollback(); } // prepare the message array $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } // set the action if not set $transaction_type = 'delete'; // log the transaction results if (file_exists(dirname(__DIR__, 2) . '/app/database_transactions/app_config.php')) { $sql = 'insert into ' . self::TABLE_PREFIX . 'database_transactions '; $sql .= '('; $sql .= 'database_transaction_uuid, '; if (isset($this->domain_uuid) && is_uuid($this->domain_uuid)) { $sql .= 'domain_uuid, '; } if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $sql .= 'user_uuid, '; } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $sql .= 'app_uuid, '; } if (isset($this->app_name) && !empty($this->app_name)) { $sql .= 'app_name, '; } $sql .= 'transaction_code, '; $sql .= 'transaction_address, '; $sql .= 'transaction_type, '; $sql .= 'transaction_date, '; $sql .= 'transaction_old, '; $sql .= 'transaction_new, '; $sql .= 'transaction_result '; $sql .= ')'; $sql .= 'values '; $sql .= '('; $sql .= "'" . uuid() . "', "; if (isset($this->domain_uuid) && is_uuid($this->domain_uuid)) { $sql .= "'" . $this->domain_uuid . "', "; } if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $sql .= ':user_uuid, '; } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $sql .= ':app_uuid, '; } if (isset($this->app_name) && !empty($this->app_name)) { $sql .= ':app_name, '; } $sql .= "'" . $message['code'] . "', "; $sql .= ':remote_address, '; $sql .= "'" . $transaction_type . "', "; $sql .= 'now(), '; if (is_array($old_array)) { $sql .= ':transaction_old, '; } else { $sql .= 'null, '; } if (is_array($new_array)) { $sql .= ':transaction_new, '; } else { $sql .= 'null, '; } $sql .= ':transaction_result '; $sql .= ')'; $statement = $this->db->prepare($sql); if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $statement->bindParam(':user_uuid', $this->user_uuid); } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $statement->bindParam(':app_uuid', $this->app_uuid); } if (isset($this->app_name) && !empty($this->app_name)) { $statement->bindParam(':app_name', $this->app_name); } $statement->bindParam(':remote_address', $_SERVER['REMOTE_ADDR']); if (is_array($old_array)) { $old_json = json_encode($old_array, JSON_PRETTY_PRINT); $statement->bindParam(':transaction_old', $old_json); } if (is_array($new_array)) { $new_json = json_encode($new_array, JSON_PRETTY_PRINT); $statement->bindParam(':transaction_new', $new_json); } $result = json_encode($this->message, JSON_PRETTY_PRINT); $statement->bindParam(':transaction_result', $result); $statement->execute(); unset($sql); } return $retval; } // end function toggle /** * Converts a plural English word to singular. * * @param string $word English word * * @return string Singular version of English word * @internal Moved to class to conserve resources. */ public static function singular(string $word) { // "-es" is used for words that end in "-x", "-s", "-z", "-sh", "-ch" in which case you add if (substr($word, -2) == 'es') { if (substr($word, -4) == 'sses') { // eg. 'addresses' to 'address' return substr($word, 0, -2); } elseif (substr($word, -3) == 'ses') { // eg. 'databases' to 'database' (necessary!) return substr($word, 0, -1); } elseif (substr($word, -3) == 'ies') { // eg. 'countries' to 'country' return substr($word, 0, -3) . 'y'; } elseif (substr($word, -3, 1) == 'x') { return substr($word, 0, -2); } elseif (substr($word, -3, 1) == 's') { return substr($word, 0, -2); } elseif (substr($word, -3, 1) == 'z') { return substr($word, 0, -2); } elseif (substr($word, -4, 2) == 'sh') { return substr($word, 0, -2); } elseif (substr($word, -4, 2) == 'ch') { return substr($word, 0, -2); } else { return rtrim($word, 's'); } } else { return rtrim($word, 's'); } } // save method /** * Get Relations searches through all fields to find relations * * @param string $schema Table name * * @return array Returns array or false * @internal Moved to class to conserve resources. */ public static function get_relations($schema) { // remove the v_ prefix if (substr($schema, 0, strlen(self::TABLE_PREFIX)) == self::TABLE_PREFIX) { $schema = substr($schema, strlen(self::TABLE_PREFIX)); } // sanitize the values $schema = self::sanitize($schema); // get the apps array $config_list = []; $directories = ['core', 'app']; $applications = [$schema, self::singular($schema)]; foreach ($directories as $directory) { foreach ($applications as $application) { $path = dirname(__DIR__, 2) . "/$directory/$application/app_config.php"; $app_config_files = glob($path); if ($app_config_files !== false) { $config_list = array_merge($config_list, $app_config_files); } } } $x = 0; foreach ($config_list as $config_path) { include ($config_path); $x++; } // search through all fields to find relations if (!empty($apps) && is_array($apps)) { foreach ($apps as $x => $app) { foreach ($app['db'] as $y => $row) { foreach ($row['fields'] as $z => $field) { if (!empty($field['deprecated']) && $field['deprecated'] != 'true') { if (!empty($field['key']['type']) && $field['key']['type'] == 'foreign') { if ($row['table']['name'] == self::TABLE_PREFIX . $schema || $field['key']['reference']['table'] == self::TABLE_PREFIX . $schema) { // get the field name if (!empty($field['name']) && is_array($field['name'])) { $field_name = trim($field['name']['text']); } else { $field_name = trim($field['name']); } // build the array $relations[$i]['table'] = $row['table']['name']; $relations[$i]['field'] = $field_name; $relations[$i]['key']['type'] = $field['key']['type']; $relations[$i]['key']['table'] = $field['key']['reference']['table']; $relations[$i]['key']['field'] = $field['key']['reference']['field']; if (isset($field['key']['reference']['action'])) { $relations[$i]['key']['action'] = $field['key']['reference']['action']; } // increment the value $i++; } } } unset($field_name); } } } } // return the array if (!empty($relations) && is_array($relations)) { return $relations; } else { return false; } } /** * Performs a select query on database using the $sql statement supplied. * * @param string $sql Valid SQL statement. * @param array|null $parameters Value can be array, empty string, or null. * @param string $return_type Values can be set to all, row, or column. * * @return mixed Returned values can be array, string, boolean, int, or false. This is dependent on * $return_type. */ public function select(string $sql, ?array $parameters = [], string $return_type = 'all') { // connect to the database if needed if (!$this->db) { $this->connect(); } // unable to connect to the database if (!$this->db) { $error_message = "Connection Failed
\n"; $error_message .= 'line number ' . __line__ . "
\n"; $message['message'] = $error_message; $this->message = $message; return false; } // set the error mode if ($this->db) { $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } // reduce prepared statement latency if ($this->db && defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { $this->db->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } // execute the query and return the results try { $prep_statement = $this->db->prepare($sql); if (is_array($parameters)) { $prep_statement->execute($parameters); } else { $prep_statement->execute(); } $message['message'] = 'OK'; $message['code'] = '200'; $message['sql'] = $sql; if (is_array($parameters)) { $message['parameters'] = $parameters; } $this->message = $message; // return the results switch ($return_type) { case 'all': return $prep_statement->fetchAll(PDO::FETCH_ASSOC); case 'row': return $prep_statement->fetch(PDO::FETCH_ASSOC); case 'column': return $prep_statement->fetchColumn(); default: return $prep_statement->fetchAll(PDO::FETCH_ASSOC); } } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } /** * Sets the object $result to sql array * * @param array $array Array containing the table name, uuid, SQL and where clause. * * @return database Returns the database object or null. */ public function find_new(array $array) { // define the message ordinal id $m = 0; // connect to the database if needed if (!$this->db) { $this->connect(); } // set the name if (isset($array['name'])) { $this->name = $array['name']; } // set the uuid if (isset($array['uuid'])) { $this->uuid = $array['uuid']; } // build the query $sql = 'SELECT * FROM ' . self::TABLE_PREFIX . $this->name . ' '; if (isset($this->uuid)) { // get the specific uuid $sql .= 'WHERE ' . self::singular($this->name) . "_uuid = '" . $this->uuid . "' "; } else { // where $i = 0; if (isset($array['where'])) { foreach ($array['where'] as $row) { if (isset($row['operator'])) { // validate the operator switch ($row['operator']) { case '<': break; case '>': break; case '<=': break; case '>=': break; case '=': break; case '<>': break; case '!=': break; default: // invalid operator return null; } // build the sql if ($i == 0) { $sql .= 'WHERE ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['value'] . ' '; } else { $sql .= 'AND ' . $row['name'] . ' ' . $row['operator'] . ' :' . $row['value'] . ' '; } } // add the name and value to the params array $params[$row['name']] = $row['value']; // increment $i $i++; } } // order by if (isset($array['order_by'])) { $array['order_by'] = self::sanitize($array['order_by']); $sql .= 'ORDER BY ' . $array['order_by'] . ' '; } // limit if (isset($array['limit']) && is_numeric($array['limit'])) { $sql .= 'LIMIT ' . $array['limit'] . ' '; } // offset if (isset($array['offset']) && is_numeric($array['offset'])) { $sql .= 'OFFSET ' . $array['offset'] . ' '; } } // execute the query, and return the results try { $prep_statement = $this->db->prepare($sql); $prep_statement->execute($params); $message['message'] = 'OK'; $message['code'] = '200'; $message['details'][$m]['name'] = $this->name; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; $message['details'][$m]['sql'] = $sql; $this->message = $message; $this->result = $prep_statement->fetchAll(PDO::FETCH_NAMED); unset($prep_statement); $m++; } catch (PDOException $e) { $message['message'] = 'Bad Request'; $message['code'] = '400'; $message['details'][$m]['name'] = $this->name; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['sql'] = $sql; $this->message = $message; $this->result = ''; $m++; } return $this; } /** * Stores the passed UUID in the object * * @param string $uuid A valid UUID must be passed * * @return database Returns this object */ public function uuid(string $uuid) { $this->uuid = $uuid; return $this; } /** * Copies records and appends suffix to the column description data * * @param array $array Three dimensional Array. The first dimension is the table name without the prefix 'v_'. * Second dimension in the row value as int. Third dimension is the column name. * * @return bool Returns true on success and false on failure. */ public function copy(array $array, $suffix = '(Copy)') { // set default return value $retval = false; // return the array if (!is_array($array)) { return $retval; } // initialize array $copy_array = []; // set the message id $m = 0; // loop through the array $x = 0; foreach ($array as $parent_name => $tables) { if (is_array($tables)) { foreach ($tables as $id => $row) { // prepare the variables $parent_name = self::sanitize($parent_name); $parent_key_name = self::singular($parent_name) . '_uuid'; // build the copy array if (!empty($row['checked']) && $row['checked'] == 'true') { // set checked to true $checked = true; // copy the child data if (!empty($row[$parent_key_name]) && is_uuid($row[$parent_key_name])) { $copy_array[$parent_name][$x][$parent_key_name] = $row[$parent_key_name]; } // remove the row from the main array unset($array[$parent_name][$x]); // loop through the fields foreach ($row as $field_name => $field_value) { // find the child tables if (is_array($field_value)) { // prepare the variables $child_name = self::sanitize($field_name); $child_key_name = self::singular($child_name) . '_uuid'; // loop through the child rows $y = 0; foreach ($field_value as $sub_row) { // delete the child data $copy_array[$child_name][][$child_key_name] = $sub_row[$child_key_name]; // remove the row from the main array unset($array[$parent_name][$x][$child_name][$y]); // increment the value $y++; } } } } // increment the value $x++; } } } // get the current data if (count($copy_array) > 0) { // build an array of tables, fields, and values foreach ($copy_array as $table_name => $rows) { foreach ($rows as $row) { foreach ($row as $field_name => $field_value) { $keys[$table_name][$field_name][] = $field_value; } } } // unset the array unset($array); // use the array to get a copy of the parent data before deleting it foreach ($copy_array as $table_name => $rows) { foreach ($rows as $row) { $table_name = self::sanitize($table_name); $sql = 'select * from ' . self::TABLE_PREFIX . $table_name . ' '; $i = 0; foreach ($row as $field_name => $field_value) { if ($i == 0) { $sql .= 'where '; } else { $sql .= 'and '; } $sql .= $field_name . ' in ( '; $i = 0; foreach ($keys[$table_name][$field_name] as $field_value) { $field_name = self::sanitize($field_name); if ($i > 0) { $sql .= ' ,'; } $sql .= ' :' . $field_name . '_' . $i . ' '; $i++; } $sql .= ') '; $i = 0; foreach ($keys[$table_name][$field_name] as $field_value) { $parameters[$field_name . '_' . $i] = $field_value; $i++; } } } $results = $this->execute($sql, $parameters, 'all'); unset($parameters); if (is_array($results)) { $array[$table_name] = $results; } } // add child data to the old array foreach ($copy_array as $parent_name => $rows) { // get relations array $relations = self::get_relations($parent_name); // loop through the rows $x = 0; foreach ($rows as $row) { if (is_array($relations)) { foreach ($relations as $relation) { // set the child table $child_table = $relation['table']; // remove the v_ prefix if (substr($child_table, 0, strlen(self::TABLE_PREFIX)) == self::TABLE_PREFIX) { $child_table = substr($child_table, strlen(self::TABLE_PREFIX)); } // get the child data $sql = 'select * from ' . self::TABLE_PREFIX . $child_table . ' '; $sql .= 'where ' . $relation['field'] . ' = :' . $relation['field']; $parameters[$relation['field']] = $row[$relation['field']]; $results = $this->execute($sql, $parameters, 'all'); unset($parameters); if (is_array($results)) { $array[$parent_name][$x][$child_table] = $results; } } } $x++; } } } // update the parent and child keys $checked = false; $x = 0; foreach ($array as $parent_name => $tables) { if (is_array($tables)) { foreach ($tables as $id => $row) { // prepare the variables $parent_name = self::sanitize($parent_name); $parent_key_name = self::singular($parent_name) . '_uuid'; $parent_key_value = uuid(); // update the parent key id $array[$parent_name][$x][$parent_key_name] = $parent_key_value; // set enabled if (array_key_exists(self::singular($parent_name) . '_enabled', $array[$parent_name][$x])) { $array[$parent_name][$x][self::singular($parent_name) . '_enabled'] = $row[self::singular($parent_name) . '_enabled'] === true || $row[self::singular($parent_name) . '_enabled'] == 'true' ? 'true' : 'false'; } elseif (array_key_exists('enabled', $array[$parent_name][$x])) { $array[$parent_name][$x]['enabled'] = $row['enabled'] === true || $row['enabled'] == 'true' ? 'true' : 'false'; } // add copy to the description if (array_key_exists(self::singular($parent_name) . '_description', $array[$parent_name][$x])) { $array[$parent_name][$x][self::singular($parent_name) . '_description'] = trim($array[$parent_name][$x][self::singular($parent_name) . '_description'] . ' ' . $suffix); } elseif (array_key_exists('description', $array[$parent_name][$x])) { $array[$parent_name][$x]['description'] = trim($array[$parent_name][$x]['description'] . ' ' . $suffix); } // loop through the fields foreach ($row as $field_name => $field_value) { // find the child tables $y = 0; if (is_array($field_value)) { // prepare the variables $child_name = self::sanitize($field_name); $child_key_name = self::singular($child_name) . '_uuid'; // loop through the child rows foreach ($field_value as $sub_row) { // update the parent key id $array[$parent_name][$x][$child_name][$y][$parent_key_name] = $parent_key_value; // udpate the child key id $array[$parent_name][$x][$child_name][$y][$child_key_name] = uuid(); // increment the value $y++; } } } // increment the value $x++; } } } // save the copy of the data if (is_array($array) && count($array) > 0) { $retval = $this->save($array); unset($array); } return $retval; } /** *

Save an array to the database.

*

Usage Example:

$row = 0;
$array['mytable'][$row]['mycolumn'] = "myvalue";
if * ($database->save($array)) {
  echo "Saved Successfully.";
} else {
  echo "Save * Failed.";
}

* * * @param array $array Three dimensional Array. The first dimension is the table name without the prefix 'v_'. * Second dimension in the row value as int. Third dimension is the column name. * @param bool $transaction_save * * @return returns an array with result details */ public function save(array &$array, bool $transaction_save = true) { // prepare the values $parent_field_names = []; $child_field_names = []; $this->message = []; $parent_key_exists = false; $parent_key_name = null; $parent_key_value = null; $child_key_exists = false; $child_key_name = null; $child_key_value = null; $table_name = null; $child_table_name = null; // set default return value $retval = true; // return the array if (!is_array($array)) { return false; } // set the message id $m = 0; // debug sql // $this->debug["sql"] = true; // connect to the database if needed if (!$this->db) { $this->connect(); } // use a try catch around the transaction try { // start the atomic transaction $this->db->beginTransaction(); // loop through the array if (is_array($array)) foreach ($array as $parent_name => $parent_array) { // get the application name and uuid if (class_exists($parent_name) && defined("$parent_name::app_name")) { $this->app_name = $parent_name::app_name; $this->app_uuid = $parent_name::app_uuid; } // process the parent array, use it to create insert and update SQL statements if (is_array($parent_array)) foreach ($parent_array as $row_id => $parent_field_array) { // set the variables $parent_name = self::sanitize($parent_name); $table_name = self::TABLE_PREFIX . $parent_name; $parent_key_name = self::singular($parent_name) . '_uuid'; $parent_key_name = self::sanitize($parent_key_name); // if the UUID is set, then set parent key exists and value // determine if the parent_key_exists $parent_key_exists = false; if (isset($parent_field_array[$parent_key_name])) { $parent_key_value = $parent_field_array[$parent_key_name]; $parent_key_exists = true; } else { if (isset($this->uuid)) { $parent_key_exists = true; $parent_key_value = $this->uuid; } else { $parent_key_value = uuid(); } } // allow characters found in the UUID only. $parent_key_value = self::sanitize($parent_key_value); // get the parent field names $parent_field_names = []; if (is_array($parent_field_array)) { foreach ($parent_field_array as $key => $value) { if (!is_array($value)) { $parent_field_names[] = self::sanitize($key); } } } // determine action update or delete, and get the original data if ($parent_key_exists) { $sql = 'SELECT ' . implode(', ', $parent_field_names) . ' FROM ' . $table_name . ' '; $sql .= 'WHERE ' . $parent_key_name . " = '" . $parent_key_value . "'; "; $prep_statement = $this->db->prepare($sql); if ($prep_statement) { // get the data try { $prep_statement->execute(); $parent_results = $prep_statement->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { $message['type'] = 'error'; $message['code'] = $e->getCode(); $message['message'] = $e->getMessage(); $message['sql'] = $sql; $this->message = $message; return false; } // set the action if (count($parent_results) > 0) { $action = 'update'; } else { $action = 'add'; } } unset($prep_statement); } else { $action = 'add'; } // add a record if ($action == 'add') { if (permission_exists(self::singular($parent_name) . '_add')) { // add to the old and new arrays $old_array = null; $new_array[$parent_name][] = $parent_field_array; // prepare the insert statement $params = []; $sql = 'INSERT INTO ' . $table_name . ' '; $sql .= '('; if (!$parent_key_exists) { $sql .= $parent_key_name . ', '; } if (is_array($parent_field_array)) { foreach ($parent_field_array as $array_key => $array_value) { if (!is_array($array_value)) { $array_key = self::sanitize($array_key); if ($array_key != 'insert_user' && $array_key != 'insert_date' && $array_key != 'update_user' && $array_key != 'update_date') { $sql .= $array_key . ', '; } } } } $sql .= 'insert_date, '; $sql .= 'insert_user '; $sql .= ') '; $sql .= 'VALUES '; $sql .= '('; if (!$parent_key_exists) { $sql .= ':parent_key_value, '; $params['parent_key_value'] = $parent_key_value; } if (is_array($parent_field_array)) { foreach ($parent_field_array as $array_key => $array_value) { if (!is_array($array_value)) { if ($array_key != 'insert_user' && $array_key != 'insert_date' && $array_key != 'update_user' && $array_key != 'update_date') { if (!isset($array_value) || $array_value == '') { $sql .= 'null, '; } elseif ($array_value === 'now()') { $sql .= 'now(), '; } elseif ($array_value === 'user_uuid()') { $sql .= ':' . $array_key . ', '; $params[$array_key] = $this->user_uuid ?? null; } elseif ($array_value === 'remote_address()') { $sql .= ':' . $array_key . ', '; $params[$array_key] = $_SERVER['REMOTE_ADDR']; } elseif (gettype($array_value) === 'boolean') { $sql .= ':' . $array_key . ', '; $params[$array_key] = $array_value; } else { $sql .= ':' . $array_key . ', '; if (gettype($array_value) === 'string') { $array_value = trim($array_value); } $params[$array_key] = $array_value; } } } } } $sql .= 'now(), '; $sql .= ':insert_user '; $sql .= ');'; // add insert user parameter $params['insert_user'] = $this->user_uuid ?? null; // set the error mode $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // reduce prepared statement latency if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { $this->db->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } // run the query and return the results try { $prep_statement = $this->db->prepare($sql); $prep_statement->execute($params); unset($prep_statement); $message['message'] = 'OK'; $message['code'] = '200'; $message['uuid'] = $parent_key_value; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; $message['details'][$m]['uuid'] = $parent_key_value; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } catch (PDOException $e) { $retval = false; $message['message'] = 'Bad Request'; $message['code'] = '400'; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['array'] = $parent_field_array; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } unset($sql); } else { $retval = false; $message['name'] = $this->app_name; $message['message'] = "Forbidden, does not have '" . self::singular($parent_name) . "_add'"; $message['code'] = '403'; $message['line'] = __line__; $this->message[] = $message; $m++; } } // edit a specific uuid if ($action == 'update') { if (permission_exists(self::singular($parent_name) . '_edit')) { // validate changes $data_modified = false; if (is_array($parent_field_array)) { $i = 0; foreach ($parent_field_array as $array_key => $array_value) { // skip child array if (is_array($array_value)) { continue; } // get the variable type of the value $database_field_type = gettype($parent_results[$i][$array_key]); $user_field_type = gettype($array_value); // trim the string and update the value if ($user_field_type === 'string') { // trim the string $array_value = trim($array_value); // update the user value $parent_field_array[$array_key] = $array_value; } // normalize the data to match the database if ($database_field_type !== $user_field_type) { // normalize null if ($array_value === '') { $array_value = null; } // normalize string if ($database_field_type === 'string') { $array_value = (string) $array_value; } // normalize numeric if ($database_field_type === 'numeric') { $array_value = intval($array_value); } // normalize boolean if ($database_field_type === 'boolean') { if ($array_value === 'true') { $array_value = true; } else { $array_value = false; } } } // verify if the data in the database has been modified if ($parent_results[$i][$array_key] !== $array_value) { // not matched // echo "$parent_name.$array_key ".($parent_results[$i][$array_key])." != ".$array_value."\n\n"; $data_modified = true; break; } // increment the id $i; } } // parent data - process the modified data if ($data_modified) { // remove the child array and update the special values if (is_array($parent_field_array)) { foreach ($parent_field_array as $array_key => $array_value) { if (is_array($array_value)) { continue; } $array_key = self::sanitize($array_key); if (!isset($array_value) || (isset($array_value) && $array_value === '')) { $temp_array[$array_key] = null; } elseif ($array_value === 'now()') { $temp_array[$array_key] = $array_value; } elseif ($array_value === 'user_uuid()') { $temp_array[$array_key] = $this->user_uuid ?? null; } elseif ($array_value === 'remote_address()') { $temp_array[$array_key] = $_SERVER['REMOTE_ADDR']; } else { if (gettype($array_value) === 'string') { $array_value = trim($array_value); } $temp_array[$array_key] = $array_value; } } } // add to the old and new arrays $old_array[$parent_name] = $parent_results; $new_array[$parent_name][] = $temp_array; // empty the temp array unset($temp_array); // prepare the update statement $params = []; $sql = 'UPDATE ' . $table_name . ' SET '; if (is_array($parent_field_array)) { foreach ($parent_field_array as $array_key => $array_value) { if (is_array($array_value)) { continue; } if ($array_key != $parent_key_name) { $array_key = self::sanitize($array_key); if (!isset($array_value) || (isset($array_value) && $array_value === '')) { $sql .= $array_key . ' = null, '; } elseif ($array_value === 'now()') { $sql .= $array_key . ' = now(), '; } elseif ($array_value === 'user_uuid()') { $sql .= $array_key . ' = :' . $array_key . ', '; $params[$array_key] = $this->user_uuid ?? null; } elseif ($array_value === 'remote_address()') { $sql .= $array_key . ' = :' . $array_key . ', '; $params[$array_key] = $_SERVER['REMOTE_ADDR']; } elseif (gettype($array_value) === 'boolean') { $sql .= $array_key . ' = :' . $array_key . ', '; $params[$array_key] = $array_value; } else { $sql .= $array_key . ' = :' . $array_key . ', '; if (gettype($array_value) === 'string') { $array_value = trim($array_value); } $params[$array_key] = $array_value; } } } } // add the modified date and user $sql .= 'update_date = now(), '; $sql .= 'update_user = :update_user '; $params['update_user'] = $this->user_uuid ?? null; // add the where with the parent name and value $sql .= 'WHERE ' . $parent_key_name . ' = :parent_key_value; '; $params['parent_key_value'] = $parent_key_value; $sql = str_replace(', WHERE', ' WHERE', $sql); // add update user parameter $params['update_user'] = $this->user_uuid ?? null; // set the error mode $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // reduce prepared statement latency if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { $this->db->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } // run the query and return the results try { $prep_statement = $this->db->prepare($sql); $prep_statement->execute($params); $message['message'] = 'OK'; $message['code'] = '200'; $message['uuid'] = $parent_key_value; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; $message['details'][$m]['uuid'] = $parent_key_value; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; unset($sql); } catch (PDOException $e) { $retval = false; $message['message'] = 'Bad Request'; $message['code'] = '400'; $message['details'][$m]['name'] = $this->app_name; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } } else { $message['details'][$m]['name'] = $parent_name; $message['details'][$m]['message'] = 'No Changes'; $message['details'][$m]['code'] = '000'; $message['details'][$m]['uuid'] = $parent_key_value; $this->message = $message; $m++; } } else { $retval = false; $message['message'] = "Forbidden, does not have '" . self::singular($parent_name) . "_edit'"; $message['code'] = '403'; $message['line'] = __line__; $this->message = $message; $m++; } } // unset the variables unset($sql, $action); // child data if (is_array($parent_field_array)) { foreach ($parent_field_array as $key => $value) { if (is_array($value)) { $child_table_name = self::TABLE_PREFIX . $key; $child_table_name = self::sanitize($child_table_name); foreach ($value as $id => $row) { // prepare the variables $child_name = self::singular($key); $child_name = self::sanitize($child_name); $child_key_name = $child_name . '_uuid'; // determine if the parent key exists in the child array $parent_key_exists = false; if (!isset($parent_field_array[$parent_key_name])) { $parent_key_exists = true; } // determine if the uuid exists $uuid_exists = false; if (is_array($row)) foreach ($row as $k => $v) { if ($child_key_name == $k) { if (strlen($v) > 0) { if (gettype($v) === 'string') { $v = trim($v); } $child_key_value = $v; $uuid_exists = true; break; } } else { $uuid_exists = false; } } // allow characters found in the uuid only if (isset($child_key_value)) { $child_key_value = self::sanitize($child_key_value); } // get the child field names $child_field_names = []; if (is_array($row)) { foreach ($row as $k => $v) { if (!is_array($v) && $k !== 'checked') { $child_field_names[] = self::sanitize($k); } } } // determine sql update or delete and get the original data if ($uuid_exists) { $sql = 'SELECT ' . implode(', ', $child_field_names) . ' FROM ' . $child_table_name . ' '; $sql .= 'WHERE ' . $child_key_name . " = '" . $child_key_value . "'; "; try { $prep_statement = $this->db->prepare($sql); if ($prep_statement) { // get the data $prep_statement->execute(); $child_results = $prep_statement->fetch(PDO::FETCH_ASSOC); // set the action if (is_array($child_results)) { $action = 'update'; } else { $action = 'add'; } } unset($prep_statement); } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } else { $action = 'add'; } // update the child data if ($action == 'update') { if (permission_exists($child_name . '_edit')) { // validate changes $data_modified = false; if (is_array($row)) { foreach ($row as $k => $v) { // sanitize the key $k = self::sanitize($k); // get the variable type of the value $database_field_type = gettype($child_results[$k]); $user_field_type = gettype($v); // trim the string if ($user_field_type === 'string') { $v = trim($v); } // normalize the data to match the database if ($database_field_type !== $user_field_type) { // normalize null if ($v === '') { $v = null; } // normalize string if ($database_field_type === 'string') { $v = (string) $v; } // normalize numeric if ($database_field_type === 'numeric') { $v = intval($v); } // normalize boolean if ($database_field_type === 'boolean') { if ($v === 'true') { $v = true; } else { $v = false; } } } // verify if the data in the database has been modified if ($child_results[$k] !== $v) { // not matched // echo "$child_name.$k ".($child_results[$k])." != ".$v."\n\n"; $data_modified = true; break; } } } // child data - process the modified data if ($data_modified) { // update the special values if (is_array($row)) { foreach ($row as $k => $v) { // sanitize the key $k = self::sanitize($k); // save the key value pairs to the temp_array if (!isset($v) || (isset($v) && $v == '')) { $temp_array[$k] = null; } elseif ($v === 'now()') { $temp_array[$k] = 'now()'; } elseif ($v === 'user_uuid()') { $temp_array[$k] = $this->user_uuid ?? null; } elseif ($v === 'remote_address()') { $temp_array[$k] = $_SERVER['REMOTE_ADDR']; } if (gettype($v) === 'boolean') { if ($v) { $v = true; } else { $v = false; } $temp_array[$k] = $v; } else { if (gettype($v) === 'string') { $v = trim($v); } $temp_array[$k] = $v; } } } // add to the old and new arrays $old_array[$key][] = $child_results; $new_array[$key][] = $temp_array; // empty the temp array unset($temp_array); // update the child data $sql = 'UPDATE ' . $child_table_name . ' SET '; if (is_array($row)) { foreach ($row as $k => $v) { if (!is_array($v) && ($k != $parent_key_name || $k != $child_key_name)) { $k = self::sanitize($k); if (!isset($v) || (isset($v) && $v == '')) { $sql .= $k . ' = null, '; } elseif ($v === 'now()') { $sql .= $k . ' = now(), '; } elseif ($v === 'user_uuid()') { $sql .= $k . ' = :' . $k . ', '; $params[$k] = $this->user_uuid ?? null; } elseif ($v === 'remote_address()') { $sql .= $k . ' = :' . $k . ', '; $params[$k] = $_SERVER['REMOTE_ADDR']; } elseif (gettype($v) === 'boolean') { $sql .= $k . ' = :' . $k . ', '; $params[$k] = $v; } else { $sql .= $k . ' = :' . $k . ', '; if (gettype($v) === 'string') { $v = trim($v); } $params[$k] = $v; } } } } // add the modified date and user $sql .= 'update_date = now(), '; $sql .= 'update_user = :update_user '; $params['update_user'] = $this->user_uuid ?? null; // add the where with the parent name and value $sql .= 'WHERE ' . $parent_key_name . ' = :parent_key_value '; $sql .= 'AND ' . $child_key_name . ' = :child_key_value; '; $params['parent_key_value'] = $parent_key_value; $params['child_key_value'] = $child_key_value; $sql = str_replace(', WHERE', ' WHERE', $sql); // set the error mode $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // reduce prepared statement latency if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { $this->db->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } try { $prep_statement = $this->db->prepare($sql); $prep_statement->execute($params); unset($prep_statement); $message['details'][$m]['name'] = $key; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; $message['details'][$m]['uuid'] = $child_key_value; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } catch (PDOException $e) { $retval = false; if ($message['code'] == '200') { $message['message'] = 'Bad Request'; $message['code'] = '400'; } $message['details'][$m]['name'] = $key; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } } else { $message['details'][$m]['name'] = $key; $message['details'][$m]['message'] = 'No Changes'; $message['details'][$m]['code'] = '000'; $message['details'][$m]['uuid'] = $child_key_value; $this->message = $message; $m++; } } else { $retval = false; $message['name'] = $child_name; $message['message'] = "Forbidden, does not have '" . $child_name . "_edit'"; $message['code'] = '403'; $message['line'] = __line__; $this->message = $message; $m++; } } // action update // add the child data if ($action == 'add') { if (permission_exists($child_name . '_add')) { // determine if child or parent key exists $child_key_name = $child_name . '_uuid'; $parent_key_exists = false; $child_key_exists = false; if (is_array($row)) { foreach ($row as $k => $v) { if ($k == $parent_key_name) { $parent_key_exists = true; } if ($k == $child_key_name) { $child_key_exists = true; if (gettype($v) === 'string') { $v = trim($v); } $child_key_value = $v; } } } if (!isset($child_key_value) || $child_key_value == '') { $child_key_value = uuid(); } // add to the old and new arrays $old_array = null; $new_array[$child_name][] = $row; // build the insert $sql = 'INSERT INTO ' . $child_table_name . ' '; $sql .= '('; if (!$parent_key_exists) { $sql .= self::singular($parent_key_name) . ', '; } if (!$child_key_exists) { $sql .= self::singular($child_key_name) . ', '; } if (is_array($row)) { foreach ($row as $k => $v) { if (!is_array($v)) { $k = self::sanitize($k); if ($k != 'insert_user' && $k != 'insert_date' && $k != 'update_user' && $k != 'update_date') { $sql .= $k . ', '; } } } } $sql .= 'insert_date, '; $sql .= 'insert_user '; $sql .= ') '; $sql .= 'VALUES '; $sql .= '('; if (!$parent_key_exists) { $sql .= ':parent_key_value, '; $params['parent_key_value'] = $parent_key_value; } if (!$child_key_exists) { $sql .= ':child_key_value, '; $params['child_key_value'] = $child_key_value; } if (is_array($row)) { foreach ($row as $k => $v) { if (!is_array($v)) { if ($k != 'insert_user' && $k != 'insert_date' && $k != 'update_user' && $k != 'update_date') { if (!isset($v) || strlen($v) == 0) { $sql .= 'null, '; } elseif ($v === 'now()') { $sql .= 'now(), '; } elseif ($v === 'user_uuid()') { $sql .= ':' . $k . ', '; $params[$k] = $this->user_uuid ?? null; } elseif ($v === 'remote_address()') { $sql .= ':' . $k . ', '; $params[$k] = $_SERVER['REMOTE_ADDR']; } elseif (gettype($v) === 'boolean') { $sql .= ':' . $k . ', '; $params[$k] = $v; } else { $k = self::sanitize($k); if ($k != 'insert_user' && $k != 'insert_date' && $k != 'update_user' && $k != 'update_date') { $sql .= ':' . $k . ', '; if (gettype($v) === 'string') { $v = trim($v); } $params[$k] = $v; } } } } } } $sql .= 'now(), '; $sql .= ':insert_user '; $sql .= ');'; // add insert user parameter $params['insert_user'] = $this->user_uuid ?? null; // set the error mode $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // reduce prepared statement latency if (defined('PDO::PGSQL_ATTR_DISABLE_PREPARES')) { $this->db->setAttribute(PDO::PGSQL_ATTR_DISABLE_PREPARES, true); } // run the query and return the results try { $prep_statement = $this->db->prepare($sql); $prep_statement->execute($params); unset($prep_statement); $message['code'] = '200'; $message['details'][$m]['name'] = $key; $message['details'][$m]['message'] = 'OK'; $message['details'][$m]['code'] = '200'; $message['details'][$m]['uuid'] = $child_key_value; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } catch (PDOException $e) { $retval = false; if ($message['code'] == '200') { $message['message'] = 'Bad Request'; $message['code'] = '400'; } $message['details'][$m]['name'] = $key; $message['details'][$m]['message'] = $e->getMessage(); $message['details'][$m]['code'] = '400'; $message['details'][$m]['sql'] = $sql; if (is_array($params)) { $message['details'][$m]['params'] = $params; } unset($params); $this->message = $message; $m++; } } else { $retval = false; $message['name'] = $child_name; $message['message'] = "Forbidden, does not have '" . $child_name . "_add'"; $message['code'] = '403'; $message['line'] = __line__; $this->message = $message; $m++; } } // action add // unset the variables unset($sql, $action, $child_key_name, $child_key_value); } // foreach value } // is array } // foreach array } } // foreach schema_array } // foreach main array // save the message $this->message = $message; // commit the atomic transaction $this->db->commit(); } catch (PDOException $e) { // rollback the transaction on error if ($this->db->inTransaction()) { $this->db->rollback(); } // prepare the message array $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } // set the action if not set if (empty($action)) { if (!empty($old_array)) { $transaction_type = 'update'; } else { $transaction_type = 'add'; } } else { $transaction_type = $action; } // debug message // echo "old\n"; // view_array($old_array, false); // echo "new\n"; // view_array($new_array, false); // exit; // check to see if the database was updated; update the message code if needed $database_updated = false; if (!empty($this->message['code']) && $this->message['code'] === '200') { $database_updated = true; } if (!$database_updated) { foreach ($this->message['details'] as $row) { if ($row['code'] === '200') { $database_updated = true; $message['code'] = '200'; break; } } } // log the transaction results if ($transaction_save && $database_updated && file_exists(dirname(__DIR__, 2) . '/app/database_transactions/app_config.php')) { try { // build the json string from the array if (!empty($old_array)) { $old_json = json_encode($old_array, JSON_PRETTY_PRINT); } if (!empty($new_array)) { $new_json = json_encode($new_array, JSON_PRETTY_PRINT); } // insert the transaction into the database $sql = 'insert into ' . self::TABLE_PREFIX . 'database_transactions '; $sql .= '('; $sql .= 'database_transaction_uuid, '; if (isset($this->domain_uuid) && is_uuid($this->domain_uuid)) { $sql .= 'domain_uuid, '; } if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $sql .= 'user_uuid, '; } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $sql .= 'app_uuid, '; } if (isset($this->app_name) && !empty($this->app_name)) { $sql .= 'app_name, '; } $sql .= 'transaction_code, '; $sql .= 'transaction_address, '; $sql .= 'transaction_type, '; $sql .= 'transaction_date, '; $sql .= 'transaction_old, '; $sql .= 'transaction_new, '; $sql .= 'transaction_result '; $sql .= ')'; $sql .= 'values '; $sql .= '('; $sql .= "'" . uuid() . "', "; if (isset($this->domain_uuid) && is_uuid($this->domain_uuid)) { $sql .= ':domain_uuid, '; } if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $sql .= ':user_uuid, '; } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $sql .= ':app_uuid, '; } if (isset($this->app_name) && !empty($this->app_name)) { $sql .= ':app_name, '; } $sql .= "'" . $message['code'] . "', "; $sql .= ':remote_address, '; $sql .= "'" . $transaction_type . "', "; $sql .= 'now(), '; if (!empty($old_json)) { $sql .= ':transaction_old, '; } else { $sql .= 'null, '; } if (!empty($new_json)) { $sql .= ':transaction_new, '; } else { $sql .= 'null, '; } $sql .= ':transaction_result '; $sql .= ')'; $statement = $this->db->prepare($sql); if (isset($this->domain_uuid) && is_uuid($this->domain_uuid)) { $statement->bindParam(':domain_uuid', $this->domain_uuid); } if (isset($this->user_uuid) && is_uuid($this->user_uuid)) { $statement->bindParam(':user_uuid', $this->user_uuid); } if (isset($this->app_uuid) && is_uuid($this->app_uuid)) { $statement->bindParam(':app_uuid', $this->app_uuid); } if (isset($this->app_name) && !empty($this->app_name)) { $statement->bindParam(':app_name', $this->app_name); } $statement->bindParam(':remote_address', $_SERVER['REMOTE_ADDR']); if (!empty($old_json)) { $old_json = json_encode($old_array, JSON_PRETTY_PRINT); $statement->bindParam(':transaction_old', $old_json); } if (!empty($new_json)) { $statement->bindParam(':transaction_new', $new_json); } $message = json_encode($this->message, JSON_PRETTY_PRINT); $statement->bindParam(':transaction_result', $message); $statement->execute(); unset($sql, $old_array, $old_json, $new_array, $new_json); } catch (PDOException $e) { $message['message'] = $e->getMessage(); $message['code'] = $e->getCode(); $message['line'] = $e->getLine(); $message['file'] = $e->getFile(); $message['trace'] = $e->getTraceAsString(); $message['debug'] = debug_backtrace(); $this->message = $message; return false; } } return $this->message; } /** * Toggles fields on a table using the toggle_field array values within the app object. * * @param array $array Three dimensional array. The first dimension is the table name without the prefix 'v_'. * Second dimension in the row value as int. Third dimension is the column name. * * @return bool Returns true on success and false on failure. * @depends database::save() * @depends database::get_apps() */ public function toggle(array $array) { // return the array if (!is_array($array)) { return false; } // set the message id $m = 0; // loop through the array if (!empty($array) && is_array($array)) { $x = 0; foreach ($array as $parent_name => $tables) { if (!empty($tables) && is_array($tables)) { foreach ($tables as $id => $row) { // prepare the variables $parent_name = self::sanitize($parent_name); $parent_key_name = self::singular($parent_name) . '_uuid'; // build the toggle array if (!empty($row['checked']) && $row['checked'] == 'true') { // toggle the field value // $toggle_array[$parent_name][$x][$parent_key_name] = $row[$parent_key_name]; $toggle_array[$parent_name][$x] = $row; // remove the row from the main array unset($array[$parent_name][$x]); } // loop through the fields foreach ($row as $field_name => $field_value) { // find the child tables $y = 0; if (!empty($field_value) && is_array($field_value)) { // prepare the variables $child_name = self::sanitize($field_name); $child_key_name = self::singular($child_name) . '_uuid'; // loop through the child rows foreach ($field_value as $sub_row) { // build the delete array if ($sub_row['checked'] == 'true') { // delete the child data $delete_array[$child_name][$y][$child_key_name] = $sub_row[$child_key_name]; // remove the row from the main array unset($array[$parent_name][$x][$child_name][$y]); } // increment the value $y++; } } } // increment the value $x++; } } } } // unset the original array unset($array); // get the $apps array from the installed apps from the core and mod directories if (count(self::$apps) == 0) { self::get_apps(); } // search through all fields to see if toggle field exists foreach (self::$apps as $x => $app) { if (!empty($app['db']) && is_array($app['db'])) { foreach ($app['db'] as $y => $row) { if (is_array($row['table']['name'])) { $table_name = $row['table']['name']['text']; } else { $table_name = $row['table']['name']; } if ($table_name === self::TABLE_PREFIX . $parent_name) { if (is_array($row['fields'])) { foreach ($row['fields'] as $field) { if (isset($field['toggle'])) { $toggle_field = $field['name']; $toggle_values = $field['toggle']; } } } } } } } // if the toggle field and values are empty then set defaults if (empty($toggle_field)) { $toggle_field = self::singular($parent_name) . '_enabled'; } if (empty($toggle_values)) { $toggle_values[] = 'true'; $toggle_values[] = 'false'; } // get the current values from the database foreach ($toggle_array as $table_name => $table) { $x = 0; foreach ($table as $row) { $child_name = self::sanitize($table_name); $child_key_name = self::singular($child_name) . '_uuid'; $array[$table_name][$x][$child_key_name] = $row[$child_key_name]; $array[$table_name][$x][$toggle_field] = ($row[$toggle_field] === $toggle_values[0]) ? $toggle_values[1] : $toggle_values[0]; $x++; } } unset($toggle_array); // save the array return $this->save($array); } /** * Gets the $apps array from the installed apps from the core and mod directories and writes it to self::$apps * overwriting previous values. * * @return null Does not return any values * @uses PROJECT_PATH Global variable * @uses dirname(__DIR__, 2) Global variable * @internal Moved to class to conserve resources. */ public static function get_apps() { // get the $apps array from the installed apps from the core and mod directories $config_list = glob(dirname(__DIR__, 2) . '/*/*/app_config.php'); $x = 0; if (is_array($config_list)) { foreach ($config_list as $config_path) { include ($config_path); $x++; } } self::$apps = $apps; } /** * Gets a list of database views from the file system. * * @param string $action options: list, create, drop * * @return array shows list of views, list of views that were updated */ public function views(string $action) { $files = glob(dirname(__DIR__, 2) . '/*/*/resources/database/views/*.php'); foreach ($files as $id => $file) { $view = []; try { include $file; $views[$id] = $view; } catch (Exception $e) { $views[$id]['error'] = $e->getMessage(); } finally { $views[$id]['file'] = $file; } } // view list if ($action === 'list') { return $views; } // update views if ($action === 'create') { $array = []; foreach ($views as $id => $row) { if (!empty($row['name']) && !empty($row['sql'])) { // set the variables $view_name = $row['name']; $view_sql = $row['sql']; $view_sql = str_replace(';', '', $view_sql); // $view_version = $row['version']; // $view_description = $row['description']; $sql = 'DROP VIEW ' . $view_name . "\n"; $this->execute($sql); // create and run the view sql $sql = 'CREATE VIEW ' . $view_name . " AS (\n"; $sql .= $view_sql . "\n"; $sql .= ")\n"; $this->execute($sql); // build the return array $views[$id]['result'] = $this->message; } else { // build the return array $views[$id]['result'] = 'Name or SQL empty'; } } // return views array return $views; } // drop views if ($action === 'drop') { $array = []; foreach ($views as $id => $row) { if (!empty($row['name'])) { // set the variables $view_name = $row['name']; // create and run the view sql $sql = 'DROP VIEW ' . $view_name . ';'; $this->execute($sql); // build the return array $views[$id]['result'] = 'Dropped'; } else { // build the return array $views[$id]['result'] = 'Name or SQL empty'; } } // return views array return $views; } } } // class database // addtitional functions for sqlite if (!function_exists('php_md5')) { function php_md5($string) { return md5($string); } } if (!function_exists('php_unix_time_stamp')) { function php_unix_time_stamp($string) { return strtotime($string); } } if (!function_exists('php_now')) { function php_now() { return date('Y-m-d H:i:s'); } } if (!function_exists('php_left')) { function php_left($string, $num) { return substr($string, 0, $num); } } if (!function_exists('php_right')) { function php_right($string, $num) { return substr($string, (strlen($string) - $num), strlen($string)); } } /* * //example usage * //find * $database->domain_uuid = $_SESSION["domain_uuid"]; * $database->type = $db_type; * $database->table = "v_extensions"; * $where[0]['name'] = 'domain_uuid'; * $where[0]['value'] = $_SESSION["domain_uuid"]; * $where[0]['operator'] = '='; * $database->where = $where; * $order_by[0]['name'] = 'extension'; * $database->order_by = $order_by; * $database->order_type = 'desc'; * $database->limit = '2'; * $database->offset = '0'; * $database->find(); * print_r($database->result); * //insert * $database->domain_uuid = $_SESSION["domain_uuid"]; * $database->table = "v_ivr_menus"; * $fields[0]['name'] = 'domain_uuid'; * $fields[0]['value'] = $_SESSION["domain_uuid"]; * echo $database->count(); */