rename p1

This commit is contained in:
Joey Kimsey
2025-02-03 12:29:16 -05:00
parent 394e752094
commit 20f09e6789
29 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,764 @@
<?php
/**
* classes/database.php
*
* Defines all interactions with the database.
*
* @todo - Add more than just MySQL
*
* @version 1.1.2
* @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com/libraries/Bedrock
* @license https://opensource.org/licenses/MIT [MIT LICENSE]
*/
namespace TheTempusProject\Bedrock\Classes;
use PDO;
use PDOException;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Canary\Classes\CustomException;
class Database {
public static $instance = null;
private $pdo = null;
private $query = null;
private $error = false;
private $results = null;
private $count = 0;
private $maxQuery = 0;
private $totalResults = 0;
private $errorMessage = null;
private $tableBuff = null;
private $fieldBuff = null;
private $queryStatus = false;
/**
* Checks the DB connection with the provided information.
*
* @param string $host - Database Host.
* @param string $db - Database Name.
* @param string $user - Database Username.
* @param string $pass - Database Password.
*
* @return bool
*/
public static function check( $host = null, $db = null, $user = null, $pass = null ) {
if ( empty( $host ) || empty( $db ) || empty( $user ) || empty( $pass ) ) {
Debug::error( 'check::db: one or more parameters are missing.' );
return false;
}
try {
Debug::log( 'Attempting to connect to DB with supplied credentials.' );
$test = new PDO( 'mysql:host=' . $host . ';dbname=' . $db, $user, $pass );
} catch ( PDOException $Exception ) {
Debug::error( 'Cannot connect to DB with provided credentials: ' . $Exception->getMessage() );
return false;
}
return true;
}
/**
* Checks the current database in the configuration file for version verification.
*
* @return boolean
*
* @todo - Update this function to be more effective.
*/
public static function mysql() {
self::connect();
$dbVersion = self::$db->version();
preg_match( '@[0-9]+\.[0-9]+\.[0-9]+@', $dbVersion, $version );
if ( version_compare( $version[0], '10.0.0', '>' ) ) {
return true;
}
self::addError( "MySQL Version is too low! Current version is $version[0]. Version 10.0.0 or higher is required." );
return false;
}
/**
* Automatically open the DB connection with settings from our global config.
*/
private function __construct( $host = null, $name = null, $user = null, $pass = null ) {
Debug::debug( 'Class initialized: ' . get_class( $this ) );
if ( isset( $host ) && isset( $name ) && isset( $user ) && isset( $pass ) ) {
try {
Debug::log( 'Attempting to connect to DB with supplied credentials.' );
$this->pdo = new PDO( 'mysql:host=' . $host . ';dbname=' . $name, $user, $pass );
} catch ( PDOException $Exception ) {
$this->error = true;
$this->errorMessage = $Exception->getMessage();
}
}
if ( !$this->enabled() ) {
$this->error = true;
$this->errorMessage = 'Database disabled in config.';
}
if ( $this->error === false ) {
try {
Debug::debug( 'Attempting to connect to DB with config credentials.' );
$this->pdo = new PDO( 'mysql:host=' . Config::getValue( 'database/dbHost' ) . ';dbname=' . Config::getValue( 'database/dbName' ), Config::getValue( 'database/dbUsername' ), Config::getValue( 'database/dbPassword' ) );
} catch ( PDOException $Exception ) {
$this->error = true;
$this->errorMessage = $Exception->getMessage();
}
}
if ( $this->error !== false ) {
new CustomException( 'dbConnection', $this->errorMessage );
return;
}
$this->maxQuery = Config::getValue( 'database/dbMaxQuery' );
// @TODO add a toggle for this
$this->pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
Debug::debug( 'DB connection successful' );
return;
}
/**
* Checks whether the DB is enabled via the config file.
*
* @return bool - whether the db module is enabled or not.
*/
public function enabled( $type = '' ) {
if ( Config::getValue( 'database/dbEnabled' ) === true ) {
return true;
}
return false;
}
public function lastId() {
return $this->pdo->lastInsertId();
}
/**
* Checks to see if there is already a DB instance open, and if not; create one.
*
* @return function - Returns the PDO DB connection.
*/
public static function getInstance( $host = null, $name = null, $user = null, $pass = null, $new = false ) {
// used to force a new connection
if ( !empty( $host ) && !empty( $name ) && !empty( $user ) && !empty( $pass ) ) {
self::$instance = new self( $host, $name, $user, $pass );
}
if ( empty( self::$instance ) || $new ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Returns the DB version.
*
* @return bool|string
*/
public function version() {
if ( !$this->enabled() ) {
$this->error = true;
$this->errorMessage = 'Database disabled';
return false;
}
$sql = 'select version()';
if ( $this->query = $this->pdo->prepare( $sql ) ) {
try {
$this->query->execute();
} catch ( PDOException $Exception ) {
$this->error = true;
$this->errorMessage = $Exception->getMessage();
Debug::error( 'DB Version Error' );
Debug::error( $this->errorMessage );
return false;
}
return $this->query->fetchColumn();
}
return false;
}
/**
* Checks the database to see if the specified table exists.
*
* @param string $name - The name of the table to check for.
*
* @return boolean
*/
protected function tableExists( $name ) {
$name = Config::getValue( 'database/dbPrefix' ) . $name;
$this->raw( "SHOW TABLES LIKE '$name'" );
if ( $this->error ) {
Debug::error( var_export( $this->errorMessage, true ) );
return false;
}
if ( $this->count === 0 ) {
return false;
}
return true;
}
/**
* Checks first that the table exists, then checks if the specified
* column exists in the table.
*
* @param string $table - The table to search.
* @param string $column - The column to look for.
*
* @return boolean
*
* @todo - Is it necessary to check the current $fields list too?
*/
protected function columnExists( $table, $column ) {
if ( !$this->tableExists( $table ) ) {
return false;
}
$table = Config::getValue( 'database/dbPrefix' ) . $table;
$this->raw( "SHOW COLUMNS FROM `$table` LIKE '$column'" );
if ( !$this->error && $this->count === 0 ) {
return false;
}
return true;
}
/**
* Execute a raw DB query.
*
* @param string $data the query to execute
*
* @return bool
*/
public function raw( $data ) {
$this->queryReset();
if ( !$this->enabled() ) {
$this->error = true;
$this->errorMessage = 'Database disabled';
return false;
}
$this->query = $this->pdo->prepare( $data );
try {
$this->query->execute();
} catch ( PDOException $Exception ) {
$this->error = true;
$this->errorMessage = $Exception->getMessage();
Debug::warn( 'DB Raw Query Error' );
Debug::warn( $this->errorMessage );
return false;
}
// @todo i think this will cause an error some circumstances
$this->count = $this->query->rowCount();
return true;
}
/**
* The actual Query function. This function takes our setup queries
* and send them to the database. it then properly sets our instance
* variables with the proper info from the DB, as secondary constructor
* for almost all objects in this class.
*
* @param string $sql - The SQL to execute.
* @param array $params - Any bound parameters for the query.
*
* @return object
*/
public function query( $sql, $params = [], $noFetch = false ) {
$this->queryReset();
if ( $this->pdo == false ) {
Debug::warn( 'DB::query - no database connection established' );
$this->error = true;
$this->errorMessage = 'DB::query - no database connection established';
return $this;
}
$this->query = $this->pdo->prepare( $sql );
if ( !empty( $params ) ) {
$x = 0;
foreach ( $params as $param ) {
$x++;
if ( is_array( $param ) ) {
// dv( $param );
}
$this->query->bindValue( $x, $param );
}
}
try {
$this->query->execute();
} catch ( PDOException $Exception ) {
$this->error = true;
$this->errorMessage = $Exception->getMessage();
Debug::error( 'DB Query Error' );
Debug::error( $this->errorMessage );
return $this;
}
if ( $noFetch === true ) {
$this->results = null;
$this->count = 1;
} else {
$this->results = $this->query->fetchAll( PDO::FETCH_OBJ );
$this->count = $this->query->rowCount();
}
return $this;
}
/**
* This function resets the values used for creating or modifying tables.
* Essentially a cleaner function.
*/
public function queryReset( $includeBuffers = false ) {
$this->results = null;
$this->count = 0;
$this->error = false;
$this->errorMessage = null;
$this->query = null;
if ( $includeBuffers ) {
$this->tableBuff = null;
$this->fieldBuff = null;
$this->queryStatus = false;
}
}
/**
* The action function builds all of our SQL.
*
* @todo : Clean this up.
*
* @param string $action - The type of action being carried out.
* @param string $tableName - The table name being used.
* @param array $where - The parameters for the action
* @param string $by - The key to sort by.
* @param string $direction - The direction to sort the results.
* @param array $limit - The result limit of the query.
*
* @return bool
*/
public function action( $action, $tableName, $where, $by = null, $direction = 'DESC', $reqLimit = null ) {
$this->totalResults = 0; // since this is how we paginate, it can't be reset when we exceed the max
if ( !$this->enabled() ) {
$this->error = true;
$this->errorMessage = 'Database disabled';
return $this;
}
$whereCount = count( $where );
if ( $whereCount < 3 ) {
Debug::error( 'DB::action - Not enough arguments supplied for "where" clause' );
$this->error = true;
$this->errorMessage = 'DB::action - Not enough arguments supplied for "where" clause';
return $this;
}
if ( $action == 'DELETE' ) {
$noFetch = true;
}
$tableName = Config::getValue( 'database/dbPrefix' ) . $tableName;
$sql = "{$action} FROM `{$tableName}` WHERE ";
$validOperators = ['=', '!=', '>', '<', '>=', '<=', 'LIKE', 'IS'];
$validDelimiters = ['AND', 'OR'];
$values = [];
while ( $whereCount > 2 ) {
$whereCount = $whereCount - 3;
$field = array_shift( $where );
$operator = array_shift( $where );
array_push( $values, array_shift( $where ) );
if ( !in_array( $operator, $validOperators ) ) {
Debug::error( 'DB::action - Invalid operator.' );
$this->error = true;
$this->errorMessage = 'DB::action - Invalid operator.';
return $this;
}
$sql .= "{$field} {$operator} ?";
if ( $whereCount > 0 ) {
$delimiter = array_shift( $where );
if ( !in_array( $delimiter, $validDelimiters ) ) {
Debug::error( 'DB::action - Invalid delimiter.' );
$this->error = true;
$this->errorMessage = 'DB::action - Invalid delimiter.';
return $this;
}
$sql .= " {$delimiter} ";
$whereCount--;
}
}
if ( isset( $by ) ) {
$sql .= " ORDER BY {$by} {$direction}";
}
$sqlPreLimit = $sql;
if ( !empty( $reqLimit ) ) {
$lim = implode(',',$reqLimit);
$sql .= " LIMIT {$lim}";
}
if ( isset( $values ) ) {
if ( !empty( $noFetch ) ) {
$error = $this->query( $sql, $values, true )->error();
} else {
$error = $this->query( $sql, $values )->error();
}
} else {
$error = $this->query( $sql )->error();
}
if ( $error ) {
Debug::warn( 'DB Action Error: ' );
Debug::warn( $this->errorMessage );
return $this;
}
$this->totalResults = $this->count;
if ( $this->count <= $this->maxQuery ) {
return $this;
}
Debug::warn( 'Query exceeded maximum results. Maximum allowed is ' . $this->maxQuery );
if ( !empty( $limit ) ) {
$newLimit = ( $reqLimit[0] + Pagination::perPage() );
$limit = " LIMIT {$reqLimit[0]},{$newLimit}";
} else {
$limit = ' LIMIT 0,' .Pagination::perPage();
}
$sql = $sqlPreLimit . $limit;
if ( isset( $values ) ) {
$error = $this->query( $sql, $values )->error();
} else {
$error = $this->query( $sql )->error();
}
if ( $error ) {
Debug::warn( 'DB Action Error: ' );
Debug::warn( $this->errorMessage );
}
return $this;
}
/**
* Function to insert into the DB.
*
* @param string $table - The table you wish to insert into.
* @param array $fields - The array of fields you wish to insert.
*
* @return bool
*/
public function insert( $table, $fields = [] ) {
$keys = array_keys( $fields );
$valuesSQL = null;
$x = 0;
$keysSQL = implode( '`, `', $keys );
foreach ( $fields as $value ) {
$x++;
$valuesSQL .= '?';
if ( $x < count( $fields ) ) {
$valuesSQL .= ', ';
}
}
$table = Config::getValue( 'database/dbPrefix' ) . $table;
$sql = "INSERT INTO `{$table}` (`" . $keysSQL . "`) VALUES ({$valuesSQL})";
if ( !$this->query( $sql, $fields, true )->error() ) {
return true;
}
return false;
}
/**
* Function to duplicate a database entry.
*
* @param string $table - The table you wish to duplicate the entry in.
* @param int $id - The ID of the entry you wish to duplicate.
*
* @return bool
*/
public function duplicateEntry($table, $id) {
// Get the original entry
$originalEntry = $this->action('SELECT', $table, ['ID', '=', $id])->results();
// Exclude the ID field
unset($originalEntry->ID);
// Insert the duplicated entry
if ($this->insert($table, (array) $originalEntry)) {
return true;
}
return false;
}
/**
* Function to update the database.
*
* @param string $table - The table you wish to update in.
* @param int $id - The ID of the entry you wish to update.
* @param array $fields - the various fields you wish to update
*
* @return bool
*/
public function update( $table, $id, $fields = [] ) {
$updateSQL = null;
$x = 0;
foreach ( $fields as $name => $value ) {
$x++;
$updateSQL .= "{$name} = ?";
if ( $x < count( $fields ) ) {
$updateSQL .= ', ';
}
}
$table = Config::getValue( 'database/dbPrefix' ) . $table;
$sql = "UPDATE {$table} SET {$updateSQL} WHERE ID = {$id}";
if ( !$this->query( $sql, $fields, true )->error() ) {
return true;
}
return false;
}
/**
* Deletes a series of, or a single instance(s) in the database.
*
* @param string $table - The table you are deleting from.
* @param string $where - The criteria for deletion.
*
* @return function
*/
public function delete( $table, $where ) {
return $this->action( 'DELETE', $table, $where );
}
/**
* Starts the object to create a new table if none already exists.
*
* NOTE: All tables created with this function will automatically
* have an 11 digit integer called ID added as a primary key.
*
* @param string $name - The name of the table you wish to create.
*
* @return boolean
*
* @todo - add a check for the name.
*/
public function newTable( $name, $addID = true ) {
if ( $this->tableExists( $name ) ) {
$this->tableBuff = null;
Debug::error( "Table already exists: $name" );
return false;
}
$this->queryReset( true );
$this->tableBuff = $name;
if ( $addID === true ) {
$this->addField( 'ID', 'int', 11, false );
}
return true;
}
/**
* Builds and executes a database query to to create a table
* using the current object's table name and fields.
*
* NOTE: By default: All tables have an auto incrementing primary key named 'ID'.
*
* @todo - Come back and add more versatility here.
*/
public function createTable() {
if ( empty( $this->tableBuff ) ) {
Debug::info( 'No Table set.' );
return false;
}
$table = Config::getValue( 'database/dbPrefix' ) . $this->tableBuff;
if ( $this->tableExists( $this->tableBuff ) ) {
Debug::error( "Table already exists: $table" );
return false;
}
$queryBuff = "CREATE TABLE `$table` (";
$x = 0;
$y = count( $this->fieldBuff );
while ( $x < $y ) {
$queryBuff .= $this->fieldBuff[$x];
$x++;
$queryBuff .= ( $x < $y ) ? ',' : '';
}
$queryBuff .= ') ENGINE=InnoDB DEFAULT CHARSET=latin1; ALTER TABLE `' . $table . '` ADD PRIMARY KEY (`ID`); ';
$queryBuff .= 'ALTER TABLE `' . $table . "` MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary index value';";
$this->queryStatus = ( $this->raw( $queryBuff ) ? true : false );
return $this->queryStatus;
}
public function removeTable( $name ) {
if ( !$this->tableExists( $name ) ) {
Debug::error( "No table exists: $name" );
return false;
}
$table = Config::getValue( 'database/dbPrefix' ) . $name;
$this->queryStatus = ( $this->raw( 'DROP TABLE `' . $table . '`' ) ? true : false );
return $this->queryStatus;
}
/**
* This function allows you to add a new field to be
* added to a previously specified table.
*
* @param string $name - The name of the field to add
* @param string $type - The type of field.
* @param integer $length - The maximum length value for the field.
* @param boolean $null - Whether or not the field can be null
* @param string $default - The default value to use for new entries if any.
* @param string $comment - DB comment for this field.
*
* @return boolean
*
* @todo - add more error reporting and checks
* use switch/cases?
*/
public function addField( $name, $type, $length, $null = true, $default = null, $comment = '' ) {
if ( empty( $this->tableBuff ) ) {
Debug::info( 'No Table set.' );
return false;
}
if ( $this->columnExists( $this->tableBuff, $name ) ) {
Debug::error( "Column already exists: $this->tableBuff > $name" );
return false;
}
if ( $null === true ) {
$sDefault = ' DEFAULT NULL';
} else {
$sDefault = ' NOT NULL';
if ( !empty( $default ) ) {
$sDefault .= " DEFAULT '$default'";
}
}
if ( !empty( $length ) ) {
if ( is_int( $length ) ) {
$sType = $type . '(' . $length . ')';
} elseif ( is_string( $length ) && ctype_digit( $length ) ) {
$sType = $type . '(' . $length . ')';
} else {
$sType = $type;
}
} else {
$sType = $type;
}
if ( !empty( $comment ) ) {
$sComment = " COMMENT '$comment'";
} else {
$sComment = '';
}
$this->fieldBuff[] = ' `' . $name . '` ' . $sType . $sDefault . $sComment;
return true;
}
public function searchColumn( $table, $column, $param ) {
return $this->action( 'SELECT *', $table, [$column, 'LIKE', '%' . $param . '%'] );
}
public function search( $table, $columns, $param ) {
if ( empty( $columns ) || ! is_array( $columns ) ) {
Debug::log( 'No columns provided for search' );
return [];
}
$conditions = [];
foreach ( $columns as $column ) {
$conditions[] = $column;
$conditions[] = 'LIKE';
$conditions[] = '%' . $param . '%';
$conditions[] = 'OR';
}
array_pop( $conditions );
return $this->action( 'SELECT *', $table, $conditions );
// return $this->action( 'SELECT ' . implode( ',', $columns ), $table, $conditions ); // need to find a way to casually make this the default....
}
/**
* Selects data from the database.
*
* @param string $table - The table we wish to select from.
* @param string $where - The criteria we wish to select.
* @param string $by - The key we wish to order by.
* @param string $direction - The direction we wish to order the results.
*
* @return function
*/
public function get( $table, $where, $by = 'ID', $direction = 'DESC', $limit = null ) {
if ( $where === '*' ) {
$where = ['ID', '>=', '0'];
}
return $this->action( 'SELECT *', $table, $where, $by, $direction, $limit );
}
/**
* Selects data from the database and automatically builds the pagination filter for the results array.
*
* @param string $table - The table we wish to select from.
* @param string $where - The criteria we wish to select.
* @param string $by - The key we wish to order by.
* @param string $direction - The direction we wish to order the results.
*
* @return function
*/
public function getPaginated( $table, $where, $by = 'ID', $direction = 'DESC', $limit = null ) {
if ( $where === '*' ) {
$where = ['ID', '>=', '0'];
}
$db = $this->action( 'SELECT ID', $table, $where, $by, $direction );
Pagination::updatePaginationTotal( $this->totalResults );
if ( ! is_array( $limit ) ) {
$limit = [ Pagination::getMin(), Pagination::getMax() ];
}
Pagination::paginate();
return $this->action( 'SELECT *', $table, $where, $by, $direction, $limit );
}
/**
* Function for returning the entire $results array.
*
* @return array - Returns the current query's results.
*/
public function results() {
return $this->results;
}
/**
* Function for returning the first result in the results array.
*
* @return array - Returns the current first member of the results array.
*/
public function first() {
if ( !empty( $this->results[0] ) ) {
return $this->results[0];
}
return false;
}
/**
* Function for returning current results' row count.
*
* @return int - Returns the current instance's SQL result count.
*/
public function count() {
return $this->count;
}
/**
* Returns if there are errors with the current query or not.
*
* @return bool
*/
public function error() {
return $this->error;
}
/**
* Returns if there are errors with the current query or not.
*
* @return bool
*/
public function errorMessage() {
//$this->query->errorInfo();
return $this->errorMessage;
}
/**
* Returns the boolean status of the most recently executed query.
*
* @return boolean
*/
public function getStatus() {
return $this->queryStatus;
}
}