*/ namespace Bitweaver; require_once EXTERNAL_LIBS_PATH.'adodb/adodb.inc.php'; // require_once EXTERNAL_LIBS_PATH.'adodb/session/adodb-session.php'; /** * This code must execute before adodb/adodb.inc.php runs * Otherwsie $ADODB_CACHE_DIR ends up being set to '/tmp' */ global $ADODB_CACHE_DIR; if( empty( $ADODB_CACHE_DIR )) { $ADODB_CACHE_DIR = sys_get_temp_dir().'/php/adodb/'.$_SERVER['HTTP_HOST'].'/'; } KernelTools::mkdir_p( $ADODB_CACHE_DIR ); /** * This class is used for database access and provides a number of functions to help * with database portability. * * Currently used as a base class, this class should be optional to ensure bitweaver * continues to function correctly, without a valid database connection. * * @package kernel */ class BitDbAdodb extends BitDb { public function __construct( $pConnectionHash = null ) { global $ADODB_FETCH_MODE; if( $pConnectionHash === null ) { global $gBitDbType, $gBitDbHost, $gBitDbUser, $gBitDbPassword, $gBitDbName; $pConnectionHash['db_type'] = $gBitDbType; $pConnectionHash['db_host'] = $gBitDbHost; $pConnectionHash['db_user'] = $gBitDbUser; $pConnectionHash['db_password'] = $gBitDbPassword; $pConnectionHash['db_name'] = $gBitDbName; } parent::__construct(); // Get all the ADODB stuff included if( !defined( "ADODB_ASSOC_CASE" )) { define( "ADODB_ASSOC_CASE", 0 ); } if( !defined( "ADODB_FETCH_MODE" )) { define( "ADODB_FETCH_MODE", ADODB_ASSOC_CASE ); } $ADODB_FETCH_MODE = ADODB_FETCH_ASSOC; if( !empty( $pConnectionHash['db_name'] ) && !empty( $pConnectionHash['db_type'] ) ) { if( $pConnectionHash['db_type'] == 'oci8' ) { $pConnectionHash['db_type'] = 'oci8po'; } $this->mType = $pConnectionHash['db_type']; $this->mName = $pConnectionHash['db_name']; if( !isset( $this->mName )) { die( "No database name specified" ); } $this->preDBConnection(); $this->mDb = ADONewConnection( $pConnectionHash['db_type'] ); $this->mDb->pdoParameters = [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]; $this->mDb->Connect( $pConnectionHash['db_host'], $pConnectionHash['db_user'], $pConnectionHash['db_password'], $pConnectionHash['db_type'] != 'pdo' ? $pConnectionHash['db_name'] : NULL ); if( !$this->mDb ) { die( "Unable to login to the database $pConnectionHash[db_type] on $pConnectionHash[db_host] as `user` $pConnectionHash[db_user]

".$this->mDb->ErrorMsg() ); } $this->postDBConnection(); unset( $pDSN ); if( defined( "DB_PERFORMANCE_STATS" ) && constant( "DB_PERFORMANCE_STATS" )) { $this->mDb->LogSQL(); } } $this->debug( $this->getDebugLevel() ); } /** * Used to create tables - most commonly from package/schema_inc.php files * @todo remove references to BIT_DB_PREFIX, us a member function * @param array pTables an array of tables and creation information in DataDict * style * @param array pOptions an array of options used while creating the tables * @return bool true|false * true if created with no errors | false if errors are stored in $this->mFailed */ public function createTables( array $pTables, array $pOptions = [] ): bool { // If server support InnoDB for MySql set the selected engine if( isset( $_SESSION['use_innodb'] )) { $pOptions = $_SESSION['use_innodb'] == true ? [ ...$pOptions, 'MYSQL' => 'ENGINE=INNODB'] : [ ...$pOptions, 'MYSQL' => 'ENGINE=MYISAM']; } $dict = NewDataDictionary( $this->mDb, 'firebird' ); $this->mFailed = []; $result = true; foreach( array_keys( $pTables ) AS $tableName ) { $completeTableName = ( defined( "BIT_DB_PREFIX" )) ? BIT_DB_PREFIX.$tableName : $tableName; $sql = $dict->CreateTableSQL($completeTableName, $pTables[$tableName], $pOptions); if( $sql && ( $dict->ExecuteSQLArray( $sql ) > 0 )) { // Success } else { // Failure $result = false; array_push( $this->mFailed, $sql.": ".$this->mDb->ErrorMsg() ); } } return $result; } /** * Used to check if tables already exists. * @todo should be used to confirm tables are already created * @param string pTable the table name * @return bool true if table already exists */ public function tableExists( string $pTable ): bool { $dict = NewDataDictionary( $this->mDb, 'firebird' ); $pTable = preg_replace( "/`/", "", $pTable ); $tables = $dict->MetaTables( ); return array_search( $pTable, $tables ) !== false; } /** * Used to drop tables * @todo remove references to BIT_DB_PREFIX, us a member function * @param array pTables an array of table names to drop * @return bool true | false * true if dropped with no errors | * false if errors are stored in $this->mFailed */ public function dropTables( array $pTables ): bool { $dict = NewDataDictionary( $this->mDb, 'firebird' ); $this->mFailed = []; $return = true; foreach( $pTables AS $tableName ) { $completeTableName = ( defined( "BIT_DB_PREFIX" )) ? BIT_DB_PREFIX.$tableName : $tableName; $sql = $dict->DropTableSQL( $completeTableName ); if( $sql && ( $dict->ExecuteSQLArray( $sql ) > 0 )) { //echo "Success
"; } else { //echo "Failure
"; $return = false; array_push($this->mFailed, $sql); } } return $return; } /** * Quotes a string to be sent to the database which is * passed to function on to AdoDB->qstr(). * @todo not sure what its supposed to do * @param string pStr string to be quotes * @return string quoted string using AdoDB->qstr() */ public function qstr( string $pStr ): string { return $this->mDb->qstr( $pStr ); } /** * Returns SUBSTRING function appropiate for database. * @return string using AdoDB->substr property */ public function substr() { return $this->mDb->substr; } /** * Returns MOD function appropiate for database. * @return string using AdoDB->mod property */ public function mod_function() { return $this->mDb->mod; } /** * Returns RANDOM function appropiate for database. * Overrides BitDb::random() * @return string using AdoDB->random property */ public function random() { return $this->mDb->random; } /** Queries the database, returning an error if one occurs, rather * than exiting while printing the error. -rlpowell * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param string pError the error string to modify and return * @param array pValues an array of values used in a parameterised query * @param int pNumRows the number of rows (LIMIT) to return in this query * @param int pOffset the row number to begin returning rows from. Used in * @return array an AdoDB RecordSet object * conjunction with $pNumRows * @todo currently not used anywhere. */ public function queryError( string $pQuery, string &$pError, ?array $pValues = null, int $pNumRows = -1, int $pOffset = -1 ): array { $this->convertQuery( $pQuery ); $result = $pNumRows == -1 && $pOffset == -1 ? $this->mDb->Execute($pQuery, $pValues) : $this->mDb->SelectLimit($pQuery, $pNumRows, $pOffset, $pValues); if( !$result ) { $pError = $this->mDb->ErrorMsg(); $result=[]; } //count the number of queries made $this->mNumQueries++; //$this->debugger_log($pQuery, $pValues); return $result; } /** Queries the database reporting an error if detected * than exiting while printing the error. -rlpowell * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array|null pValues an array of values used in a parameterised query * @param int pNumRows the number of rows (LIMIT) to return in this query * @param int pOffset the row number to begin returning rows from. Used in * conjunction with $pNumRows * @return \ADORecordSet|null an AdoDB RecordSet object */ public function query( $query, $values = false, $numrows = BIT_QUERY_DEFAULT, $offset = BIT_QUERY_DEFAULT, $pCacheTime=BIT_QUERY_DEFAULT ) { $this->convertQuery( $query ); if( empty( $this->mDb )) { return null; } $values = $values === null ? false : $values; $this->queryStart(); if( !is_numeric( $numrows )) { $numrows = BIT_QUERY_DEFAULT; } if( !is_numeric( $offset )) { $offset = BIT_QUERY_DEFAULT; } $result = $numrows == BIT_QUERY_DEFAULT && $offset == BIT_QUERY_DEFAULT ? ( !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->Execute( $query, $values ) : $this->mDb->CacheExecute( $pCacheTime, $query, $values ) ) : ( !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->SelectLimit( $query, $numrows, $offset, $values ) : $this->mDb->CacheSelectLimit( $pCacheTime, $query, $numrows, $offset, $values ) ); $this->queryComplete(); return $result; } /** * List columns in a database as an array of ADOFieldObjects. * See top of file for definition of object. * * @param string tabletable name to query * @param bool upper uppercase table name (required by some databases) * @param bool schema is optional database schema to use - not supported by all databases. * * @return array of ADOFieldObjects for current table. */ public function MetaColumns( string $table, bool $normalize=true, bool $schema=false ): array { $table = str_replace( '`', '', $table ); return $this->mDb->MetaColumns( $table, $normalize ); } /** * List indexes in a database as an array of ADOFieldObjects. * See top of file for definition of object. * * @param string table table name to query * @param bool primary list primary indexes * @param bool owner list owner of index * * @return array of ADOFieldObjects for current table. */ public function MetaIndexes( string $table, bool $primary=false, bool $owner=false): array { $table = str_replace( '`', '', $table ); return $this->mDb->MetaIndexes( $table, $primary, $owner ); } /** * getAll * * @param string $pQuery * @param array $pValues * @param numeric $pCacheTime * @access public * @return array|bool true on success, false on failure - mErrors will contain reason for failure */ public function getAll( $pQuery, $pValues = [], $pCacheTime=BIT_QUERY_DEFAULT ) { if( empty( $this->mDb )) { return false; } $this->queryStart(); $this->convertQuery( $pQuery ); $result = !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->GetAll( $pQuery, $pValues ) : $this->mDb->CacheGetAll($pCacheTime, $pQuery, $pValues ); //count the number of queries made $this->queryComplete(); return $result; } /** Executes the SQL and returns all elements of the first column as a 1-dimensional array. The recordset is discarded for you automatically. If an error occurs, false is returned. * See AdoDB GetCol() function for more detail. * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array pValues an array of values used in a parameterised query * @param bool pForceArray if set to true, when an array is created for each value * @param bool pFirst2Cols if set to true, only returns the first two columns * @return array|bool the associative array, or false if an error occurs * @todo not currently used anywhere */ public function getCol( $pQuery, $pValues = false, $pTrim=false, $pCacheTime=BIT_QUERY_DEFAULT ) { if( empty( $this->mDb )) { return false; } $this->queryStart(); $this->convertQuery( $pQuery ); $result = !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->GetCol( $pQuery, $pValues, $pTrim ) : $this->mDb->CacheGetCol( $pCacheTime, $pQuery, $pValues, $pTrim ); //count the number of queries made $this->queryComplete(); return $result; } /** Returns an associative array for the given query. * See AdoDB GetAssoc() function for more detail. * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array pValues an array of values used in a parameterised query * @param bool pForceArray if set to true, when an array is created for each value * @param bool pFirst2Cols if set to true, only returns the first two columns * @return array|bool associative array, or false if an error occurs */ public function getArray( $pQuery, $pValues = false, $pForceArray=false, $pFirst2Cols=false, $pCacheTime=BIT_QUERY_DEFAULT ) { if( empty( $this->mDb )) { return false; } $this->queryStart(); $this->convertQuery( $pQuery ); $result = !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->GetArray( $pQuery, $pValues ) : $this->mDb->CacheGetArray( $pCacheTime, $pQuery, $pValues ); $this->queryComplete(); return $result; } /** Returns an associative array for the given query. * See AdoDB GetAssoc() function for more detail. * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array pValues an array of values used in a parameterised query * @param bool pForceArray if set to true, when an array is created for each value * @param bool pFirst2Cols if set to true, only returns the first two columns * @return array|false the associative array, or false if an error occurs */ public function getAssoc( $pQuery, $pValues = false, $pForceArray=false, $pFirst2Cols=false, $pCacheTime=BIT_QUERY_DEFAULT ) { if( empty( $this->mDb )) { return false; } $this->queryStart(); $this->convertQuery( $pQuery ); $result = !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->GetAssoc( $pQuery, $pValues, $pForceArray, $pFirst2Cols ) : $this->mDb->CacheGetAssoc( $pCacheTime, $pQuery, $pValues, $pForceArray, $pFirst2Cols ); $this->queryComplete(); return $result; } /** Executes the SQL and returns the first row as an array. The recordset and remaining rows are discarded for you automatically. If an error occurs, false is returned. * See AdoDB GetRow() function for more detail. * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array pValues an array of values used in a parameterised query * @return array returns the first row as an array, or false if an error occurs */ public function getRow( $pQuery, $pValues = [], $pCacheTime=BIT_QUERY_DEFAULT ) { if( empty( $this->mDb ) ) { return []; } $this->queryStart(); $this->convertQuery($pQuery); $result = !$this->isCachingActive() || $pCacheTime == BIT_QUERY_DEFAULT ? $this->mDb->GetRow( $pQuery, $pValues ) : $this->mDb->CacheGetRow( $pCacheTime, $pQuery, $pValues ); $this->queryComplete(); return $result; } /** Returns a single column value from the database. * @param string pQuery the SQL query. Use backticks (`) to quote all table * and attribute names for AdoDB to quote appropriately. * @param array pValues an array of values used in a parameterised query * @param int pOffset the row number to begin returning rows from. * @return string the associative array, or false if an error occurs */ public function getOne( $pQuery, $pValues = [], $pNumRows=BIT_QUERY_DEFAULT, $pOffset=BIT_QUERY_DEFAULT, $pCacheTime = BIT_QUERY_DEFAULT ) { $result = $this->query($pQuery, $pValues, 1, $pOffset, $pCacheTime ); $res = ( $result != null ) ? $result->fetchRow() : false; if( $res === false ) { //simulate pears behaviour return ''; } $ret = current( $res ); return $ret; } /** * A database portable Sequence management function. * * @param string pSequenceName Name of the sequence to be used * It will be created if it does not already exist * @return 0 if not supported, otherwise a sequence id */ public function GenID( $pSequenceName, $pUseDbPrefix = true ) { if( empty( $this->mDb )) { return false; } $prefix = $pUseDbPrefix ? str_replace( "`", "", BIT_DB_PREFIX ) : ''; return $this->mDb->GenID( $prefix.$pSequenceName ); } /** * A database portable Sequence management function. * * @param string pSequenceName Name of the sequence to be used * It will be created if it does not already exist * @param int pStartID Allows setting the initial value of the sequence * @return bool 0 if not supported, otherwise a sequence id * @todo To be combined with GenID */ public function CreateSequence( $pSeqname='adodbseq',$startID=1 ) { if( empty( $this->mDb->_genSeqSQL )) { return false; } return $this->mDb->CreateSequence( $pSeqname, $startID ); } /** * A database portable Sequence management function. * * @param string pSequenceName Name of the sequence to be dropped * @return false if not supported */ public function DropSequence( $pSeqname='adodbseq' ) { if( empty( $this->mDb->_dropSeqSQL )) { return false; } return $this->mDb->DropSequence( $pSeqname ); } /** * A database portable IFnull function. * * @param string pField argument to compare to null * @param string pNullRepl the null replacement value * @return string that represents the function that checks whether * $pField is null for the given database, and if null, change the * value returned to $pNullRepl. */ public function ifNull($pField, $pNullRepl): string { return $this->mDb->ifNull( $pField, $pNullRepl ); } /** Format the timestamp in the format the database accepts. * @param int pDate a Unix integer timestamp or an ISO format Y-m-d H:i:s * @return string the timestamp as a quoted string. * @todo could be used to later convert all int timestamps into db * timestamps. Currently not used anywhere. */ public function ls( $pDate ) { // not sure what this did - maybe someone can comment why its here //return preg_replace("/'/","", $this->mDb->DBTimeStamp($pDate)); return $this->mDb->DBTimeStamp($pDate); } /** * Format date column in sql string given an input format that understands Y M D */ function SQLDate( $pDateFormat, $pBaseDate=false ) { return $this->mDb->SQLDate( $pDateFormat, $pBaseDate ); } /** * Calculate the offset of a date for a particular database and generate * appropriate SQL. Useful for calculating future/past dates and storing * in a database. * @param float pDays Number of days to offset by * If dayFraction=1.5 means 1.5 days from now, 1.0/24 for 1 hour. * @param string pColumn Value to be offset * If null an offset from the current time is supplied * @return string New number of days * * @todo Not currently used - this is database specific and uses TIMESTAMP * rather than unix seconds */ public function OffsetDate( $pDays, $pColumn=null ) { return $this->mDb->OffsetDate( $pDays, $pColumn ); } /** Converts backtick (`) quotes to the appropriate quote for the * database. * @private * @param string pQuery the SQL query using backticks (`) * @return void the correctly quoted SQL statement * @todo investigate replacement by AdoDB NameQuote() function */ public function convertQuery( string &$pQuery ): void { if( !empty( $this->mType )) { switch( $this->mType ) { case "oci8": // convert bind variables - adodb does not do that $qe = explode( "?", $pQuery ); $pQuery = ""; for( $i = 0; $i < sizeof($qe) - 1; $i++ ) { $pQuery .= $qe[$i] . ":" . $i; } $pQuery .= $qe[$i]; default: parent::convertQuery( $pQuery ); break; } } } /** will activate ADODB's native debugging output * @param bool|int pLevel debugging level - false is off, true is on, 99 is verbose **/ public function debug( bool|int $pLevel=99 ): void { parent::debug( $pLevel ); if( is_object( $this->mDb )) { $this->mDb->debug = $pLevel; } } /** returns the level of query debugging output * @return bool|int pLevel debugging level - false is off, true is on, 99 is verbose **/ public function getDebugLevel(): bool|int { return $this->mDebug ?? false; } /** * Improved method of initiating a transaction. Used together with CompleteTrans(). * Advantages include: * * a. StartTrans/CompleteTrans is nestable, unlike BeginTrans/CommitTrans/RollbackTrans. * Only the outermost block is treated as a transaction.
* b. CompleteTrans auto-detects SQL errors, and will rollback on errors, commit otherwise.
* c. All BeginTrans/CommitTrans/RollbackTrans inside a StartTrans/CompleteTrans block * are disabled, making it backward compatible. */ function StartTrans() { return is_object( $this->mDb ) && $this->mDb->StartTrans(); } /** * Used together with StartTrans() to end a transaction. Monitors connection * for sql errors, and will commit or rollback as appropriate. * * autoComplete if true, monitor sql errors and commit and rollback as appropriate, * and if set to false force rollback even if no SQL error detected. * @return bool true on commit, false on rollback. */ function CompleteTrans() { return is_object( $this->mDb ) && $this->mDb->CompleteTrans(); } /** * If database does not support transactions, rollbacks always fail, so return false * otherwise returns true if the Rollback was successful * * @return bool true/false. */ function RollbackTrans() { $this->mDb->FailTrans(); return $this->mDb->CompleteTrans( false ); } /** * Create a list of tables available in the current database * * @param bool|string ttype can either be 'VIEW' or 'TABLE' or false. * If false, both views and tables are returned. * "VIEW" returns only views * "TABLE" returns only tables * @param bool showSchema returns the schema/user with the table name, eg. USER.TABLE * @param bool mask is the input mask - only supported by oci8 and postgresql * * @return array of tables for current database. */ public function MetaTables( bool|string $ttype = false, bool $showSchema = false, bool $mask = false ): bool|array { return $this->mDb->MetaTables( $ttype, $showSchema, $mask ); } /** * @return # rows affected by UPDATE/DELETE */ function Affected_Rows() { return $this->mDb->Affected_Rows(); } } /* * Custom ADODB Error Handler. This will be called with the following params * * @param $dbms the RDBMS you are connecting to * @param $fn the name of the calling function (in uppercase) * @param $errno the native error number from the database * @param $errmsg the native error msg from the database * @param $p1 $fn specific parameter - see below * @param $P2 $fn specific parameter - see below */ function bitdb_error_handler( $dbms, $fn, $errno, $errmsg, $p1, $p2, &$thisConnection ) { global $gBitDb; if( ini_get( 'error_reporting' ) == 0 ) { return; // obey @ protocol } $dbParams = [ 'db_type'=>$dbms, 'call_func'=>$fn, 'errno'=>$errno, 'db_msg'=>$errmsg, 'sql'=>$p1, 'p2'=>$p2, ]; $logString = bit_error_string( $dbParams ); /* * Log connection error somewhere * 0 message is sent to PHP's system logger, using the Operating System's system * logging mechanism or a file, depending on what the error_log configuration * directive is set to. * 1 message is sent by email to the address in the destination parameter. * This is the only message type where the fourth parameter, extra_headers is used. * This message type uses the same internal function as mail() does. * 2 message is sent through the PHP debugging connection. * This option is only available if remote debugging has been enabled. * In this case, the destination parameter specifies the host name or IP address * and optionally, port number, of the socket receiving the debug information. * 3 message is appended to the file destination */ error_log( $logString,0 ); $subject = $_SERVER['SERVER_NAME'] ?? 'BITWEAVER'; $fatal = false; if(( $fn == 'EXECUTE' ) && ( $thisConnection->MetaError() != -5 ) && (empty( $gBitDb ) || $gBitDb->isFatalActive()) ) { $fatal = true; } bit_display_error( $logString, $dbParams['db_msg'], $fatal ); }