Improved Code Sniffing (#928)
[openemr.git] / library / sql.inc
blobcaacb410a94b79f961453216f22758ee15e5f9c9
1 <?php
2 /**
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>.
19 * @package   OpenEMR
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");
28 /**
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
35         /**
36         * ADODB Execute function wrapper to ensure proper auditing in OpenEMR.
37         *
38         * @param  string  $sql         query
39         * @param  array   $inputarr    binded variables array (optional)
40         * @return boolean              returns false if error
41         */
42     function Execute($sql,$inputarr=false)
43     {
44         $retval= parent::Execute($sql,$inputarr);
45         if ($retval === false) {
46             $outcome = 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();
51           // Last error no
52             $GLOBALS['last_mysql_error_no']=$this->ErrorNo();
53         }
54         else {
55             $outcome = true;
56         }
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);
61         return $retval;
62     }
64         /**
65         * ADODB Execute function wrapper to skip auditing in OpenEMR.
66         *
67         * Bypasses the OpenEMR auditing engine.
68         *
69         * @param  string  $sql         query
70         * @param  array   $inputarr    binded variables array (optional)
71         * @return boolean              returns false if error
72         */
73     function ExecuteNoLog($sql,$inputarr=false)
74     {
75         return parent::Execute($sql,$inputarr);
76     }
78         /*
79         * ADODB GenID function wrapper to work with OpenEMR.
80         *
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).
87         *
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
91         */
92     function GenID($seqname='adodbseq',$startID=1)
93     {
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);
100         if (!$rs) {
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);
107         }
109         if ($rs) {
110             $this->genID = mysqli_insert_id($this->_connectionID);
111             $rs->Close();
112         } else
113                 $this->genID = 0;
115         return $this->genID;
116     }
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);
136     }
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>";
154     } else {
155         echo "Check that you can ping the server " . text($host) . ".<p>";
156     }//if local
157     HelpfulDie("Could not connect to server!", getSqlLastError());
158     exit;
159 }//if no connection
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
167 * not use binding.
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)
174 * @return recordset
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());
187     }
188     return $recordset;
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)
207 * @return recordset
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());
218     }
219     return $recordset;
223 * sqlStatement() function wrapper for CDR engine in OpenEMR.
224 * Allows option to turn on/off auditing specifically for the
225 * CDR engine.
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);
238     }
239     else {
240         return sqlStatementNoLog($statement,$binds);
241     }
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
253 * @return array
255 function sqlFetchArray($r)
257   //treat as an adodb recordset
258     if ($r === false)
259     return false;
260     if ($r->EOF)
261     return false;
262   //ensure it's an object (ie. is set)
263     if (!is_object($r))
264     return false;
266     return $r->FetchRow();
271  * Wrapper for ADODB getAssoc
273  * @see http://adodb.org/dokuwiki/doku.php?id=v5:reference:connection:getassoc
275  * @param string $sql
276  * @param string[] $bindvars
277  * @param boolean $forceArray
278  * @param boolean $first2Cols
279  * @return array
280  */
281 function sqlGetAssoc( $sql, $bindvars=false, $forceArray=false, $first2Cols=false )
284     return $GLOBALS['adodb']['db']->getAssoc( $sql, $bindvars, $forceArray, $first2Cols  );
289 * Standard sql insert query in OpenEMR.
291 * Function that will allow use of the adodb binding
292 * feature to prevent sql-injection. This function
293 * is specialized for insert function and will return
294 * the last id generated from the insert.
296 * @param  string   $statement  query
297 * @param  array    $binds      binded variables array (optional)
298 * @return integer  Last id generated from the sql insert command
300 function sqlInsert($statement, $binds=false)
302   // Below line is to avoid a nasty bug in windows.
303     if (empty($binds)) $binds = false;
305   //Run a adodb execute
306   // Note the auditSQLEvent function is embedded in the
307   //   Execute function.
308     $recordset = $GLOBALS['adodb']['db']->Execute($statement, $binds);
309     if ($recordset === false) {
310         HelpfulDie("insert failed: $statement", getSqlLastError());
311     }
312   // Return the correct last id generated using function
313   //   that is safe with the audit engine.
314     return getSqlLastID();
318 * Specialized sql query in OpenEMR that only returns
319 * the first row of query results as an associative array.
321 * Function that will allow use of the adodb binding
322 * feature to prevent sql-injection.
324 * @param  string  $statement  query
325 * @param  array   $binds      binded variables array (optional)
326 * @return array
328 function sqlQuery($statement, $binds=false)
330   // Below line is to avoid a nasty bug in windows.
331     if (empty($binds)) $binds = false;
333     $recordset = $GLOBALS['adodb']['db']->Execute( $statement, $binds );
335     if ($recordset === false) {
336         HelpfulDie("query failed: $statement", getSqlLastError());
337     }
338     if ($recordset->EOF)
339     return false;
340     $rez = $recordset->FetchRow();
341     if ($rez == false)
342     return false;
343     return $rez;
347 * Specialized sql query in OpenEMR that bypasses the auditing engine
348 * and only returns the first row of query results as an associative array.
350 * Function that will allow use of the adodb binding
351 * feature to prevent sql-injection. It is equivalent to the
352 * sqlQuery() function, EXCEPT it skips the
353 * audit engine. This function should only be used
354 * in very special situations.
356 * @param  string  $statement  query
357 * @param  array   $binds      binded variables array (optional)
358 * @return array
360 function sqlQueryNoLog($statement, $binds=false)
362   // Below line is to avoid a nasty bug in windows.
363     if (empty($binds)) $binds = false;
365     $recordset = $GLOBALS['adodb']['db']->ExecuteNoLog( $statement, $binds );
367     if ($recordset === false) {
368         HelpfulDie("query failed: $statement", getSqlLastError());
369     }
370     if ($recordset->EOF)
371     return false;
372     $rez = $recordset->FetchRow();
373     if ($rez == false)
374     return false;
375     return $rez;
379 * Specialized sql query in OpenEMR that ignores sql errors, bypasses the
380 * auditing engine and only returns the first row of query results as an
381 * associative array.
383 * Function that will allow use of the adodb binding
384 * feature to prevent sql-injection. It is equivalent to the
385 * sqlQuery() function, EXCEPT it skips the
386 * audit engine and ignores erros. This function should only be used
387 * in very special situations.
389 * @param  string  $statement  query
390 * @param  array   $binds      binded variables array (optional)
391 * @return array
393 function sqlQueryNoLogIgnoreError($statement, $binds=false)
395   // Below line is to avoid a nasty bug in windows.
396     if (empty($binds)) $binds = false;
398     $recordset = $GLOBALS['adodb']['db']->ExecuteNoLog( $statement, $binds );
400     if ($recordset === false) {
401         // ignore the error and return FALSE
402         return false;
403     }
404     if ($recordset->EOF)
405     return false;
406     $rez = $recordset->FetchRow();
407     if ($rez == false)
408     return false;
409     return $rez;
413 * sqlQuery() function wrapper for CDR engine in OpenEMR.
414 * Allows option to turn on/off auditing specifically for the
415 * CDR engine.
417 * @param  string  $statement  query
418 * @param  array   $binds      binded variables array (optional)
419 * @return array
421 function sqlQueryCdrEngine($statement, $binds=false )
423   // Below line is to avoid a nasty bug in windows.
424     if (empty($binds)) $binds = false;
426     if ($GLOBALS['audit_events_cdr']) {
427         return sqlQuery($statement,$binds);
428     }
429     else {
430         return sqlQueryNoLog($statement,$binds);
431     }
435 * Specialized sql query in OpenEMR that skips auditing.
437 * This function should only be used in very special situations.
439 * @param  string  $statement  query
441 function sqlInsertClean_audit($statement)
444     $ret = $GLOBALS['adodb']['db']->ExecuteNoLog($statement);
445     if ($ret === false) {
446         HelpfulDie("insert failed: $statement", getSqlLastError());
447     }
451 * Function that will safely return the last ID inserted,
452 * and accounts for the audit engine.
454 * @return  integer Last ID that was inserted into sql
456 function getSqlLastID()
458     return $GLOBALS['lastidado'] > 0 ? $GLOBALS['lastidado'] : $GLOBALS['adodb']['db']->Insert_ID();
462 * Function that will safely return the last error,
463 * and accounts for the audit engine.
465 * @param   string  $mode either adodb(default) or native_mysql
466 * @return  string        last mysql error
468 function getSqlLastError()
470     return !empty($GLOBALS['last_mysql_error']) ? $GLOBALS['last_mysql_error'] : $GLOBALS['adodb']['db']->ErrorMsg();
474  * Function that will safely return the last error no,
475  * and accounts for the audit engine.
477  * @param   string  $mode either adodb(default) or native_mysql
478  * @return  string        last mysql error no
479  */
480 function getSqlLastErrorNo()
482     return !empty($GLOBALS['last_mysql_error_no']) ? $GLOBALS['last_mysql_error_no'] : $GLOBALS['adodb']['db']->ErrorNo();
486 * Function that will return an array listing
487 * of columns that exist in a table.
489 * @param   string  $table sql table
490 * @return  array
492 function sqlListFields($table)
494     $sql = "SHOW COLUMNS FROM ". add_escape_custom($table);
495     $resource = sqlQ($sql);
496     $field_list = array();
497     while($row = sqlFetchArray($resource)) {
498         $field_list[] = $row['Field'];
499     }
500     return $field_list;
504 * Returns the number of sql rows
506 * @param recordset $r
507 * @return integer Number of rows
509 function sqlNumRows($r)
511     return $r->RecordCount();
515 * Error function for OpenEMR sql functions
517 * @param string $statement
518 * @param string $sqlerr
520 function HelpfulDie($statement, $sqlerr='')
523     echo "<h2><font color='red'>" . xlt('Query Error') . "</font></h2>";
525     if (!$GLOBALS['sql_string_no_show_screen']) {
526         echo "<p><font color='red'>ERROR:</font> " . text($statement) . "</p>";
527     }
529     $logMsg="SQL Error with statement:".$statement;
531     if ($sqlerr) {
532         if (!$GLOBALS['sql_string_no_show_screen']) {
533              echo "<p>Error: <font color='red'>" . text($sqlerr) . "</font></p>";
534         }
535         $logMsg.="--".$sqlerr;
536     }//if error
538     $backtrace = debug_backtrace();
540     if (!$GLOBALS['sql_string_no_show_screen']) {
541         for ($level = 1; $level < count($backtrace); $level++)
542         {
543             $info = $backtrace[$level];
544             echo "<br>" . text($info["file"] . " at " . $info["line"] . ":" . $info["function"]);
545             if ($level > 1) {
546                 echo "(" . text(implode(",", $info["args"])) . ")";
547             }
548         }
549     }
551     $logMsg.="==>".$backtrace[1]["file"]." at ".$backtrace[1]["line"].":".$backtrace[1]["function"];
553     error_log($logMsg);
555     exit;
559 * @todo document use of the generate_id function
561 function generate_id()
563     $database = $GLOBALS['adodb']['db'];
564     return $database->GenID("sequences");
568 * Deprecated function. Standard sql query in OpenEMR.
570 * Function that will allow use of the adodb binding
571 * feature to prevent sql-injection. Will continue to
572 * be compatible with previous function calls that do
573 * not use binding.
574 * It will return a recordset object.
575 * The sqlFetchArray() function should be used to
576 * utilize the return object.
578 * @deprecated
579 * @param  string  $statement  query
580 * @param  array   $binds      binded variables array (optional)
581 * @return recordset
583 function sqlQ($statement, $binds=false )
585   // Below line is to avoid a nasty bug in windows.
586     if (empty($binds)) $binds = false;
588     $recordset = $GLOBALS['adodb']['db']->Execute( $statement, $binds ) or
589     HelpfulDie("query failed: $statement", getSqlLastError());
590     return $recordset;
594 * Simple wrapper for sqlInsert() function (deprecated).
596 * Function that will allow use of the adodb binding feature
597 * to prevent sql-injection.
599 * @deprecated
600 * @param  string   $statement  query
601 * @param  array    $binds      binded variables array (optional)
602 * @return integer  Last id generated from the sql insert command
604 function idSqlStatement($statement , $binds=false )
606   // Below line is to avoid a nasty bug in windows.
607     if (empty($binds)) $binds = false;
609     return sqlInsert($statement, $binds);
613 * Simple wrapper for sqlInsert() function (deprecated).
615 * Function that will allow use of the adodb binding feature
616 * to prevent sql-injection.
618 * @deprecated
619 * @param  string   $statement  query
620 * @param  array    $binds      binded variables array (optional)
621 * @return integer  Last id generated from the sql insert command
623 function sqlInsertClean($statement, $binds=false )
625   // Below line is to avoid a nasty bug in windows.
626     if (empty($binds)) $binds = false;
628     return sqlInsert($statement, $binds);
633 * Sql close connection function (deprecated)
635 * No longer needed since PHP does this automatically.
637 * @deprecated
638 * @return boolean
640 function sqlClose()
642   //----------Close our mysql connection
643     $closed = $GLOBALS['adodb']['db']->close or
644     HelpfulDie("could not disconnect from mysql server link", getSqlLastError());
645     return $closed;
649 * Very simple wrapper function and not necessary (deprecated)
651 * Do not use.
653 * @deprecated
654 * @return connection
656 function get_db()
658     return $GLOBALS['adodb']['db'];
662  * Generic mysql select db function
663  * Used when converted to mysqli to centralize special circumstances.
664  * @param string $database
665  */
666 function generic_sql_select_db($database, $link = null)
668     if (is_null($link))
669     $link = $GLOBALS['dbh'];
670     mysqli_select_db($link, $database);
674  * Generic mysql affected rows function
675  * Used when converted to mysqli to centralize special circumstances.
677  */
678 function generic_sql_affected_rows()
680     return mysqli_affected_rows($GLOBALS['dbh']);
684  * Generic mysql insert id function
685  * Used when converted to mysqli to centralize special circumstances.
687                  */
688 function generic_sql_insert_id()
690     return mysqli_insert_id($GLOBALS['dbh']);
695  * Begin a Transaction.
696  */
697 function sqlBeginTrans()
699     $GLOBALS['adodb']['db']->BeginTrans();
704  * Commit a transaction
705  */
706 function sqlCommitTrans($ok=true)
708     $GLOBALS['adodb']['db']->CommitTrans();
713  * Rollback a transaction
714  */
715 function sqlRollbackTrans()
717     $GLOBALS['adodb']['db']->RollbackTrans();