3 * Sql functions/classes for OpenEMR.
5 * Includes classes and functions that OpenEMR uses
6 * to interact with SQL.
8 * LICENSE: This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>.
20 * @link http://www.open-emr.org
23 require_once(dirname(__FILE__) . "/sqlconf.php");
24 require_once(dirname(__FILE__) . "/../vendor/adodb/adodb-php/adodb.inc.php");
25 require_once(dirname(__FILE__) . "/../vendor/adodb/adodb-php/drivers/adodb-mysqli.inc.php");
26 require_once(dirname(__FILE__) . "/log.inc");
29 * ADODB_mysql class wrapper to ensure proper auditing in OpenEMR.
31 * @author Kevin Yeh <kevin.y@integralemr.com>
33 class ADODB_mysqli_log extends ADODB_mysqli
36 * ADODB Execute function wrapper to ensure proper auditing in OpenEMR.
38 * @param string $sql query
39 * @param array $inputarr binded variables array (optional)
40 * @return boolean returns false if error
42 function Execute($sql,$inputarr=false)
44 $retval= parent::Execute($sql,$inputarr);
45 if ($retval === false) {
47 // Stash the error into last_mysql_error so it doesn't get clobbered when
48 // we insert into the audit log.
49 $GLOBALS['last_mysql_error']=$this->ErrorMsg();
52 $GLOBALS['last_mysql_error_no']=$this->ErrorNo();
57 // Stash the insert ID into lastidado so it doesn't get clobbered when
58 // we insert into the audit log.
59 $GLOBALS['lastidado']=$this->Insert_ID();
60 auditSQLEvent($sql,$outcome,$inputarr);
65 * ADODB Execute function wrapper to skip auditing in OpenEMR.
67 * Bypasses the OpenEMR auditing engine.
69 * @param string $sql query
70 * @param array $inputarr binded variables array (optional)
71 * @return boolean returns false if error
73 function ExecuteNoLog($sql,$inputarr=false)
75 return parent::Execute($sql,$inputarr);
79 * ADODB GenID function wrapper to work with OpenEMR.
81 * Need to override to fix a bug where call to GenID was updating
82 * sequences table but always returning a zero with the OpenEMR audit
83 * engine both on and off. Note this bug only appears to occur in recent
84 * php versions on windows. The fix is to use the ExecuteNoLog() function
85 * rather than the Execute() functions within this function (otherwise,
86 * there are no other changes from the original ADODB GenID function).
88 * @param string $seqname table name containing sequence (default is adodbseq)
89 * @param integer $startID id to start with for a new sequence (default is 1)
90 * @return integer returns the sequence integer
92 function GenID($seqname='adodbseq',$startID=1)
94 // post-nuke sets hasGenID to false
95 if (!$this->hasGenID) return false;
97 $getnext = sprintf($this->_genIDSQL,$seqname);
98 $holdtransOK = $this->_transOK; // save the current status
99 $rs = @$this->ExecuteNoLog($getnext);
101 if ($holdtransOK) $this->_transOK = true; //if the status was ok before reset
102 $u = strtoupper($seqname);
103 $this->ExecuteNoLog(sprintf($this->_genSeqSQL,$seqname));
104 $cnt = $this->GetOne(sprintf($this->_genSeqCountSQL,$seqname));
105 if (!$cnt) $this->ExecuteNoLog(sprintf($this->_genSeq2SQL,$seqname,$startID-1));
106 $rs = $this->ExecuteNoLog($getnext);
110 $this->genID = mysqli_insert_id($this->_connectionID);
118 if (!defined('ADODB_FETCH_ASSOC')) define('ADODB_FETCH_ASSOC', 2);
119 $database = NewADOConnection("mysqli_log"); // Use the subclassed driver which logs execute events
120 // Below clientFlags flag is telling the mysql connection to allow local_infile setting,
121 // which is needed to import data in the Administration->Other->External Data Loads feature.
122 // Note this is a specific bug to work in Ubuntu 12.04, of which the Data Load feature does not
123 // work and is suspicious for a bug in PHP of that OS; Setting this clientFlags fixes this bug
124 // and appears to not cause problems in other operating systems.
125 $database->clientFlags = 128;
126 $database->port = $port;
127 $database->PConnect($host, $login, $pass, $dbase);
128 $GLOBALS['adodb']['db'] = $database;
129 $GLOBALS['dbh'] = $database->_connectionID;
131 // Modified 5/2009 by BM for UTF-8 project ---------
132 if (!$disable_utf8_flag) {
133 $success_flag = $database->Execute("SET NAMES 'utf8'");
134 if (!$success_flag) {
135 error_log("PHP custom error: from openemr library/sql.inc - Unable to set up UTF8 encoding with mysql database: ".getSqlLastError(), 0);
139 // Turn off STRICT SQL
140 $sql_strict_set_success = $database->Execute("SET sql_mode = ''");
141 if (!$sql_strict_set_success) {
142 error_log("Unable to set strict sql setting: ".getSqlLastError(), 0);
145 // set up associations in adodb calls (not sure why above define
146 // command does not work)
147 $GLOBALS['adodb']['db']->SetFetchMode(ADODB_FETCH_ASSOC);
149 //fmg: This makes the login screen informative when no connection can be made
150 if (!$GLOBALS['dbh']) {
151 //try to be more helpful
152 if ($host == "localhost") {
153 echo "Check that mysqld is running.<p>";
155 echo "Check that you can ping the server " . text($host) . ".<p>";
157 HelpfulDie("Could not connect to server!", getSqlLastError());
162 * Standard sql query in OpenEMR.
164 * Function that will allow use of the adodb binding
165 * feature to prevent sql-injection. Will continue to
166 * be compatible with previous function calls that do
168 * It will return a recordset object.
169 * The sqlFetchArray() function should be used to
170 * utilize the return object.
172 * @param string $statement query
173 * @param array $binds binded variables array (optional)
176 function sqlStatement($statement, $binds=false )
178 // Below line is to avoid a nasty bug in windows.
179 if (empty($binds)) $binds = false;
181 // Use adodb Execute with binding and return a recordset.
182 // Note that the auditSQLEvent function is embedded
183 // in the Execute command.
184 $recordset = $GLOBALS['adodb']['db']->Execute( $statement, $binds );
185 if ($recordset === FALSE) {
186 HelpfulDie("query failed: $statement", getSqlLastError());
192 * Specialized sql query in OpenEMR that skips auditing.
194 * Function that will allow use of the adodb binding
195 * feature to prevent sql-injection. Will continue to
196 * be compatible with previous function calls that do
197 * not use binding. It is equivalent to the
198 * sqlStatement() function, EXCEPT it skips the
199 * audit engine. This function should only be used
200 * in very special situations.
201 * It will return a recordset object.
202 * The sqlFetchArray() function should be used to
203 * utilize the return object.
205 * @param string $statement query
206 * @param array $binds binded variables array (optional)
209 function sqlStatementNoLog($statement, $binds=false )
211 // Below line is to avoid a nasty bug in windows.
212 if (empty($binds)) $binds = false;
214 // Use adodb ExecuteNoLog with binding and return a recordset.
215 $recordset = $GLOBALS['adodb']['db']->ExecuteNoLog( $statement, $binds );
216 if ($recordset === FALSE) {
217 HelpfulDie("query failed: $statement", getSqlLastError());
223 * sqlStatement() function wrapper for CDR engine in OpenEMR.
224 * Allows option to turn on/off auditing specifically for the
227 * @param string $statement query
228 * @param array $binds binded variables array (optional)
229 * @return recordset/resource
231 function sqlStatementCdrEngine($statement, $binds=false )
233 // Below line is to avoid a nasty bug in windows.
234 if (empty($binds)) $binds = false;
236 if ($GLOBALS['audit_events_cdr']) {
237 return sqlStatement($statement,$binds);
240 return sqlStatementNoLog($statement,$binds);
245 * Returns a row (as an array) from a sql recordset.
247 * Function that will allow use of the adodb binding
248 * feature to prevent sql-injection.
249 * It will act upon the object returned from the
250 * sqlStatement() function (and sqlQ() function).
252 * @param recordset $r
255 function sqlFetchArray($r)
257 //treat as an adodb recordset
262 //ensure it's an object (ie. is set)
266 return $r->FetchRow();
271 * Wrapper for ADODB getAssoc
273 * @see http://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:getassoc
276 * @param string[] $bindvars
277 * @param boolean $forceArray
278 * @param boolean $first2Cols
281 function sqlGetAssoc( $sql, $bindvars=false, $forceArray=false, $first2Cols=false ) {
283 return $GLOBALS['adodb']['db']->getAssoc( $sql, $bindvars, $forceArray, $first2Cols );
288 * Standard sql insert query in OpenEMR.
290 * Function that will allow use of the adodb binding
291 * feature to prevent sql-injection. This function
292 * is specialized for insert function and will return
293 * the last id generated from the insert.
295 * @param string $statement query
296 * @param array $binds binded variables array (optional)
297 * @return integer Last id generated from the sql insert command
299 function sqlInsert($statement, $binds=false)
301 // Below line is to avoid a nasty bug in windows.
302 if (empty($binds)) $binds = false;
304 //Run a adodb execute
305 // Note the auditSQLEvent function is embedded in the
307 $recordset = $GLOBALS['adodb']['db']->Execute($statement, $binds);
308 if ($recordset === FALSE) {
309 HelpfulDie("insert failed: $statement", getSqlLastError());
311 // Return the correct last id generated using function
312 // that is safe with the audit engine.
313 return getSqlLastID();
317 * Specialized sql query in OpenEMR that only returns
318 * the first row of query results as an associative array.
320 * Function that will allow use of the adodb binding
321 * feature to prevent sql-injection.
323 * @param string $statement query
324 * @param array $binds binded variables array (optional)
327 function sqlQuery($statement, $binds=false)
329 // Below line is to avoid a nasty bug in windows.
330 if (empty($binds)) $binds = false;
332 $recordset = $GLOBALS['adodb']['db']->Execute( $statement, $binds );
334 if ($recordset === FALSE) {
335 HelpfulDie("query failed: $statement", getSqlLastError());
339 $rez = $recordset->FetchRow();
346 * Specialized sql query in OpenEMR that bypasses the auditing engine
347 * and only returns the first row of query results as an associative array.
349 * Function that will allow use of the adodb binding
350 * feature to prevent sql-injection. It is equivalent to the
351 * sqlQuery() function, EXCEPT it skips the
352 * audit engine. This function should only be used
353 * in very special situations.
355 * @param string $statement query
356 * @param array $binds binded variables array (optional)
359 function sqlQueryNoLog($statement, $binds=false)
361 // Below line is to avoid a nasty bug in windows.
362 if (empty($binds)) $binds = false;
364 $recordset = $GLOBALS['adodb']['db']->ExecuteNoLog( $statement, $binds );
366 if ($recordset === FALSE) {
367 HelpfulDie("query failed: $statement", getSqlLastError());
371 $rez = $recordset->FetchRow();
378 * Specialized sql query in OpenEMR that ignores sql errors, bypasses the
379 * auditing engine and only returns the first row of query results as an
382 * Function that will allow use of the adodb binding
383 * feature to prevent sql-injection. It is equivalent to the
384 * sqlQuery() function, EXCEPT it skips the
385 * audit engine and ignores erros. This function should only be used
386 * in very special situations.
388 * @param string $statement query
389 * @param array $binds binded variables array (optional)
392 function sqlQueryNoLogIgnoreError($statement, $binds=false)
394 // Below line is to avoid a nasty bug in windows.
395 if (empty($binds)) $binds = false;
397 $recordset = $GLOBALS['adodb']['db']->ExecuteNoLog( $statement, $binds );
399 if ($recordset === FALSE) {
400 // ignore the error and return FALSE
405 $rez = $recordset->FetchRow();
412 * sqlQuery() function wrapper for CDR engine in OpenEMR.
413 * Allows option to turn on/off auditing specifically for the
416 * @param string $statement query
417 * @param array $binds binded variables array (optional)
420 function sqlQueryCdrEngine($statement, $binds=false )
422 // Below line is to avoid a nasty bug in windows.
423 if (empty($binds)) $binds = false;
425 if ($GLOBALS['audit_events_cdr']) {
426 return sqlQuery($statement,$binds);
429 return sqlQueryNoLog($statement,$binds);
434 * Specialized sql query in OpenEMR that skips auditing.
436 * This function should only be used in very special situations.
438 * @param string $statement query
440 function sqlInsertClean_audit($statement)
443 $ret = $GLOBALS['adodb']['db']->ExecuteNoLog($statement);
444 if ($ret === FALSE) {
445 HelpfulDie("insert failed: $statement", getSqlLastError());
450 * Function that will safely return the last ID inserted,
451 * and accounts for the audit engine.
453 * @return integer Last ID that was inserted into sql
455 function getSqlLastID() {
456 return $GLOBALS['lastidado'] > 0 ? $GLOBALS['lastidado'] : $GLOBALS['adodb']['db']->Insert_ID();
460 * Function that will safely return the last error,
461 * and accounts for the audit engine.
463 * @param string $mode either adodb(default) or native_mysql
464 * @return string last mysql error
466 function getSqlLastError() {
467 return !empty($GLOBALS['last_mysql_error']) ? $GLOBALS['last_mysql_error'] : $GLOBALS['adodb']['db']->ErrorMsg();
471 * Function that will safely return the last error no,
472 * and accounts for the audit engine.
474 * @param string $mode either adodb(default) or native_mysql
475 * @return string last mysql error no
477 function getSqlLastErrorNo() {
478 return !empty($GLOBALS['last_mysql_error_no']) ? $GLOBALS['last_mysql_error_no'] : $GLOBALS['adodb']['db']->ErrorNo();
482 * Function that will return an array listing
483 * of columns that exist in a table.
485 * @param string $table sql table
488 function sqlListFields($table) {
489 $sql = "SHOW COLUMNS FROM ". add_escape_custom($table);
490 $resource = sqlQ($sql);
491 $field_list = array();
492 while($row = sqlFetchArray($resource)) {
493 $field_list[] = $row['Field'];
499 * Returns the number of sql rows
501 * @param recordset $r
502 * @return integer Number of rows
504 function sqlNumRows($r)
506 return $r->RecordCount();
510 * Error function for OpenEMR sql functions
512 * @param string $statement
513 * @param string $sqlerr
515 function HelpfulDie ($statement, $sqlerr='')
517 echo "<p><p><font color='red'>ERROR:</font> ".text($statement)."<p>";
518 $logMsg="SQL Error with statement:".$statement;
520 echo "Error: <font color='red'>".text($sqlerr)."</font><p>";
521 $logMsg.="--".$sqlerr;
523 $backtrace=debug_backtrace();
524 for($level=1;$level<count($backtrace);$level++)
526 $info=$backtrace[$level];
527 echo "<br>".text($info["file"]." at ".$info["line"].":".$info["function"]);
529 echo "(".text(implode(",",$info["args"])).")";
532 $logMsg.="==>".$backtrace[1]["file"]." at ".$backtrace[1]["line"].":".$backtrace[1]["function"];
539 * @todo document use of the generate_id function
541 function generate_id () {
542 $database = $GLOBALS['adodb']['db'];
543 return $database->GenID("sequences");
547 * Deprecated function. Standard sql query in OpenEMR.
549 * Function that will allow use of the adodb binding
550 * feature to prevent sql-injection. Will continue to
551 * be compatible with previous function calls that do
553 * It will return a recordset object.
554 * The sqlFetchArray() function should be used to
555 * utilize the return object.
558 * @param string $statement query
559 * @param array $binds binded variables array (optional)
562 function sqlQ($statement, $binds=false )
564 // Below line is to avoid a nasty bug in windows.
565 if (empty($binds)) $binds = false;
567 $recordset = $GLOBALS['adodb']['db']->Execute( $statement, $binds ) or
568 HelpfulDie("query failed: $statement", getSqlLastError());
573 * Simple wrapper for sqlInsert() function (deprecated).
575 * Function that will allow use of the adodb binding feature
576 * to prevent sql-injection.
579 * @param string $statement query
580 * @param array $binds binded variables array (optional)
581 * @return integer Last id generated from the sql insert command
583 function idSqlStatement($statement , $binds=false )
585 // Below line is to avoid a nasty bug in windows.
586 if (empty($binds)) $binds = false;
588 return sqlInsert($statement, $binds);
592 * Simple wrapper for sqlInsert() function (deprecated).
594 * Function that will allow use of the adodb binding feature
595 * to prevent sql-injection.
598 * @param string $statement query
599 * @param array $binds binded variables array (optional)
600 * @return integer Last id generated from the sql insert command
602 function sqlInsertClean($statement, $binds=false )
604 // Below line is to avoid a nasty bug in windows.
605 if (empty($binds)) $binds = false;
607 return sqlInsert($statement, $binds);
612 * Sql close connection function (deprecated)
614 * No longer needed since PHP does this automatically.
621 //----------Close our mysql connection
622 $closed = $GLOBALS['adodb']['db']->close or
623 HelpfulDie("could not disconnect from mysql server link", getSqlLastError());
628 * Very simple wrapper function and not necessary (deprecated)
636 return $GLOBALS['adodb']['db'];
640 * Generic mysql select db function
641 * Used when converted to mysqli to centralize special circumstances.
642 * @param string $database
644 function generic_sql_select_db($database, $link = null)
647 $link = $GLOBALS['dbh'];
648 mysqli_select_db($link, $database);
652 * Generic mysql affected rows function
653 * Used when converted to mysqli to centralize special circumstances.
656 function generic_sql_affected_rows()
658 return mysqli_affected_rows($GLOBALS['dbh']);
662 * Generic mysql insert id function
663 * Used when converted to mysqli to centralize special circumstances.
666 function generic_sql_insert_id()
668 return mysqli_insert_id($GLOBALS['dbh']);
673 * Begin a Transaction.
675 function sqlBeginTrans()
677 $GLOBALS['adodb']['db']->BeginTrans();
682 * Commit a transaction
684 function sqlCommitTrans($ok=true)
686 $GLOBALS['adodb']['db']->CommitTrans();
691 * Rollback a transaction
693 function sqlRollbackTrans()
695 $GLOBALS['adodb']['db']->RollbackTrans();