minor fix in hover for prior commit
[openemr.git] / library / log.inc
blob64a8653050998df7df4d36da36ba559a7668f4f5
1 <?php
2 #require_once("{$GLOBALS['srcdir']}/sql.inc");
3 require_once(dirname(__FILE__). "/sql.inc");
4 require_once(dirname(__FILE__). "/formdata.inc.php");
6 function newEvent($event, $user, $groupname, $success, $comments="", $patient_id=null, $log_from = 'open-emr', $menu_item = 'dashboard', $ccda_doc_id = 0) {
7     $adodb = $GLOBALS['adodb']['db'];
8     $crt_user=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ?  $_SERVER['SSL_CLIENT_S_DN_CN'] : null;
10     $category = $event;
11     // Special case delete for lists table
12     if($event == 'delete')
13         $category = eventCategoryFinder($comments, $event, '');
15     // deal with comments encryption, if turned on
16     $encrypt_comment = 'No';
17     if (!empty($comments)) {
18         if ($GLOBALS["enable_auditlog_encryption"]) {
19             $comments =  aes256Encrypt($comments);
20             $encrypt_comment = 'Yes';
21         }
22     }
24     if ($log_from == 'patient-portal') {
25         $sqlMenuItems = "SELECT * FROM patient_portal_menu";
27         $resMenuItems = sqlStatement($sqlMenuItems);
28         for($iter=0; $rowMenuItem=sqlFetchArray($resMenuItems); $iter++) {
29             $menuItems[$rowMenuItem['patient_portal_menu_id']] = $rowMenuItem['menu_name'];
30         }
32         $menuItemId = array_search($menu_item, $menuItems);
33         $sql = "insert into log ( date, event,category, user, patient_id, groupname, success, comments,
34                 log_from, menu_item_id, crt_user, ccda_doc_id) values ( NOW(), ?,'Patient Portal', ?, ?, ?, ?, ?, ?, ?,?, ?)";
35         $ret = sqlStatementNoLog($sql, array($event, $user, $patient_id, $groupname, $success, $comments,$log_from, $menuItemId,$crt_user, $ccda_doc_id));
36     } else {
37     
38     /* More details added to the log */
39     $sql = "insert into log ( date, event,category, user, groupname, success, comments, crt_user, patient_id) " .
40             "values ( NOW(), " . $adodb->qstr($event) . ",". $adodb->qstr($category) . "," . $adodb->qstr($user) .
41             "," . $adodb->qstr($groupname) . "," . $adodb->qstr($success) . "," .
42             $adodb->qstr($comments) ."," .
43             $adodb->qstr($crt_user) ."," . $adodb->qstr($patient_id). ")";
45     $ret = sqlInsertClean_audit($sql);
46     }
48     // Send item to log_comment_encrypt for comment encyption tracking
49     $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
50     $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
51                      " VALUES ( ".
52                      $adodb->qstr($last_log_id) . "," .
53                      $adodb->qstr($encrypt_comment) . "," .
54                      "'')";
55     sqlInsertClean_audit($encryptLogQry);
57     if(($patient_id=="NULL") || ($patient_id==null))$patient_id=0;
59     send_atna_audit_msg($user, $groupname, $event, $patient_id, $success, $comments);
62 function getEventByDate($date, $user="", $cols="DISTINCT date, event, user, groupname, patient_id, success, comments, checksum")
64     $sql = "SELECT $cols FROM log WHERE date >= '$date 00:00:00' AND date <= '$date 23:59:59'";
65     if ($user) $sql .= " AND user LIKE '$user'";
66     $sql .= " ORDER BY date DESC LIMIT 5000";
67     $res = sqlStatement($sql);
68     for($iter=0; $row=sqlFetchArray($res); $iter++) {
69         $all[$iter] = $row;
70     }
71     return $all;
74 /******************
75  * Get records from the LOG and Extended_Log table
76  * using the optional parameters:
77  *   date : a specific date  (defaults to today)
78  *   user : a specific user  (defaults to none)
79  *   cols : gather specific columns  (defaults to date,event,user,groupname,comments)
80  *   sortby : sort the results by  (defaults to none)
81  * RETURNS:
82  *   array of results
83  ******************/
84 function getEvents($params)
86     // parse the parameters
87     $cols = "DISTINCT date, event, category, user, groupname, patient_id, success, comments,checksum,crt_user, id ";
88     if (isset($params['cols']) && $params['cols'] != "") $cols = $params['cols'];
90     $date1 = date("Y-m-d H:i:s", time());
91     if (isset($params['sdate']) && $params['sdate'] != "") $date1= $params['sdate'];
93     $date2 = date("Y-m-d H:i:s", time());
94     if (isset($params['edate']) && $params['edate'] != "") $date2= $params['edate'];
96     $user = "";
97     if (isset($params['user']) && $params['user'] != "") $user= $params['user'];
99     //VicarePlus :: For Generating log with patient id.
100     $patient = "";
101     if (isset($params['patient']) && $params['patient'] != "") $patient= $params['patient'];
103     $sortby = "";
104     if (isset($params['sortby']) && $params['sortby'] != "") $sortby = $params['sortby'];
106     $levent = "";
107     if (isset($params['levent']) && $params['levent'] != "") $levent = $params['levent'];
109      $tevent = "";
110     if (isset($params['tevent']) && $params['tevent'] != "") $tevent = $params['tevent'];
112     $direction = 'asc';
113     if (isset($params['direction']) && $params['direction'] != "") $direction = $params['direction'];
115      $event = "";
116     if (isset($params['event']) && $params['event'] != "") $event = $params['event'];
117     if ($event!=""){
118     if ($sortby == "comments") $sortby = "description";
119     if ($sortby == "groupname") $sortby = ""; //VicarePlus :: since there is no groupname in extended_log
120     if ($sortby == "success") $sortby = "";   //VicarePlus :: since there is no success field in extended_log
121     if ($sortby == "checksum") $sortby = "";  //VicarePlus :: since there is no checksum field in extended_log
122     if ($sortby == "category") $sortby = "";  //VicarePlus :: since there is no category field in extended_log
123     $columns = "DISTINCT date, event, user, recipient,patient_id,description";
124     $sql = "SELECT $columns FROM extended_log WHERE date >= '$date1' AND date <= '$date2'";
125     if ($user != "") $sql .= " AND user LIKE '$user'";
126     if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
127     if ($levent != "") $sql .= " AND event LIKE '$levent%'";
128     if ($sortby != "") $sql .= " ORDER BY ".$sortby." DESC"; // descending order
129     $sql .= " LIMIT 5000";
130     }
131     else
132     {
133     // do the query
134     $sql = "SELECT $cols FROM log WHERE date >= '$date1' AND date <= '$date2'";
135     if ($user != "") $sql .= " AND user LIKE '$user'";
136     if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
137     if ($levent != "") $sql .= " AND event LIKE '$levent%'";
138     if ($tevent != "") $sql .= " AND event LIKE '%$tevent'";
139     if ($sortby != "") $sql .= " ORDER BY ".$sortby."  ".escape_sort_order($direction); // descending order
140     $sql .= " LIMIT 5000";
141     }
142     $res = sqlStatement($sql);
143     for($iter=0; $row=sqlFetchArray($res); $iter++) {
144         $all[$iter] = $row;
145     }
146     return $all;
149 /* Given an SQL insert/update that was just performeds:
150  * - Find the table and primary id of the row that was created/modified
151  * - Calculate the SHA1 checksum of that row (with all the
152  *   column values concatenated together).
153  * - Return the SHA1 checksum as a 40 char hex string.
154  * If this is not an insert/update query, return "".
155  * If multiple rows were modified, return "".
156  * If we're unable to determine the row modified, return "".
158  * TODO: May need to incorporate the binded stuff (still analyzing)
160  */
161 function sql_checksum_of_modified_row($statement)
163     $table = "";
164     $rid = "";
166     $tokens = preg_split("/[\s,(\'\"]+/", $statement);
167     /* Identifying the id for insert/replace statements for calculating the checksum */
168         if((strcasecmp($tokens[0],"INSERT")==0) || (strcasecmp($tokens[0],"REPLACE")==0)){
169         $table = $tokens[2];
170         $rid = generic_sql_insert_id();
171         /* For handling the table that doesn't have auto-increment column */
172         if ($rid === 0 || $rid === FALSE) {
173           if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map")
174            $id="acl_id";
175           else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map")
176           $id="group_id";
177           else
178            $id="id";
179           /* To handle insert statements */
180           if($tokens[3] == $id){
181              for($i=4;$i<count($tokens);$i++){
182                  if(strcasecmp($tokens[$i],"VALUES")==0){
183                   $rid=$tokens[$i+1];
184                      break;
185                 }// if close
186               }//for close
187             }//if close
188         /* To handle replace statements */
189           else if(strcasecmp($tokens[3],"SET")==0){
190                  if((strcasecmp($tokens[4],"ID")==0) || (strcasecmp($tokens[4],"`ID`")==0)){
191                   $rid=$tokens[6];
192            }// if close
193         }
195         else {
196             return "";
197           }
198         }
199     }
200      /* Identifying the id for update statements for calculating the checksum */
201        else if(strcasecmp($tokens[0],"UPDATE")==0){
202         $table = $tokens[1];
204         $offset = 3;
205         $total = count($tokens);
207         /* Identifying the primary key column for the updated record */
208         if ($table == "form_physical_exam") {
209             $id = "forms_id";
210         }
211         else if ($table == "claims"){
212             $id = "patient_id";
213         }
214         else if ($table == "openemr_postcalendar_events") {
215             $id = "pc_eid";
216         }
217          else if ($table == "lang_languages"){
218             $id = "lang_id";
219          }
220          else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
221             $id = "pc_catid";
222          }
223          else if ($table == "openemr_postcalendar_limits"){
224             $id = "pc_limitid";
225          }
226          else if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
227            $id="acl_id";
228           }
229           else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
230           $id="group_id";
231           }
232            else {
233             $id = "id";
234            }
236          /* Identifying the primary key value for the updated record */
237         while ($offset < $total) {
238             /* There are 4 possible ways that the id=123 can be parsed:
239              * ('id', '=', '123')
240              * ('id=', '123')
241              * ('id=123')
242              * ('id', '=123')
243              */
244             $rid = "";
245            /*id=', '123'*/
246             if (($tokens[$offset] == "$id=") && ($offset + 1 < $total)) {
247                 $rid = $tokens[$offset+1];
248                 break;
249             }
250            /* 'id', '=', '123' */
251             else if ($tokens[$offset] == "$id" && $tokens[$offset+1] == "=" && ($offset+2 < $total)) {
252                 $rid = $tokens[$offset+2];
253                 break;
254              }
255             /*id=123*/
256             else if (strpos($tokens[$offset], "$id=") === 0) {
257                 $tid = substr($tokens[$offset], strlen($id)+1);
258                 if(is_numeric($tid))
259                  $rid=$tid;
260                  break;
261              }
262            /*'id', '=123' */
263              else if($tokens[$offset] == "$id") {
264                 $tid = substr($tokens[$offset+1],1);
265                 if(is_numeric($tid))
266                  $rid=$tid;
267                 break;
268               }
269             $offset += 1;
270         }//while ($offset < $total)
271     }// else if ($tokens[0] == 'update' || $tokens[0] == 'UPDATE' )
273     if ($table == "" || $rid == "") {
274         return "";
275     }
276    /* Framing sql statements for calculating checksum */
277    if ($table == "form_physical_exam") {
278         $sql = "select * from $table where forms_id = $rid";
279     }
280    else if ($table == "claims"){
281                 $sql = "select * from $table where patient_id = $rid";
282         }
283    else if ($table == "openemr_postcalendar_events") {
284             $sql = "select * from $table where pc_eid = $rid";
285         }
286     else if ($table == "lang_languages") {
287             $sql = "select * from $table where lang_id = $rid";
288     }
289     else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
290             $sql = "select * from $table where pc_catid = $rid";
291          }
292     else if ($table == "openemr_postcalendar_limits"){
293            $sql = "select * from $table where pc_limitid = $rid";
294          }
295     else if ($table ==  "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
296            $sql = "select * from $table where acl_id = $rid";
297          }
298      else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
299            $sql = "select * from $table where group_id = $rid";
300       }
301      else {
302         $sql = "select * from $table where id = $rid";
303     }
304     // When this function is working perfectly, can then shift to the
305     // sqlQueryNoLog() function.
306     $results = sqlQueryNoLogIgnoreError($sql);
307     $column_values = "";
308    /* Concatenating the column values for the row inserted/updated */
309     if (is_array($results)) {
310         foreach ($results as $field_name => $field) {
311             $column_values .= $field;
312         }
313     }
314     // ViCarePlus: As per NIST standard, the encryption algorithm SHA1 is used
316     //error_log("COLUMN_VALUES: ".$column_values,0);
317     return sha1($column_values);
320 /* Create an XML audit record corresponding to RFC 3881.
321  * The parameters passed are the column values (from table 'log')
322  * for a single audit record.
323  */
324 function create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments)
327     /* Event action codes indicate whether the event is read/write.
328      * C = create, R = read, U = update, D = delete, E = execute
329      */
330     $eventActionCode = 'E';
331     if (substr($event, -7) == "-create") {
332         $eventActionCode = 'C';
333     }
334     else if (substr($event, -7) == "-insert") {
335         $eventActionCode = 'C';
336     }
337     else if (substr($event, -7) == "-select") {
338         $eventActionCode = 'R';
339     }
340     else if (substr($event, -7) == "-update") {
341         $eventActionCode = 'U';
342     }
343     else if (substr($event, -7) == "-delete") {
344         $eventActionCode = 'D';
345     }
347     $date_obj = new DateTime();
348     $eventDateTime = $date_obj->format(DATE_ATOM);
350     /* For EventOutcomeIndicator, 0 = success and 4 = minor error */
351     $eventOutcome = ($outcome === 1) ? 0 : 4;
353     /* The choice of event codes is up to OpenEMR.
354      * We're using the same event codes as
355      * https://iheprofiles.projects.openhealthtools.org/
356      */
357     $eventIDcodeSystemName = "DCM";
358     $eventIDcode = 0;
359     $eventIDdisplayName = $event;
361     if (strpos($event, 'patient-record') !== FALSE) {
362         $eventIDcode = 110110;
363         $eventIDdisplayName = 'Patient Record';
364     }
365     else if (strpos($event, 'view') !== FALSE) {
366         $eventIDCode = 110110;
367         $eventIDdisplayName = 'Patient Record';
368     }
369     else if (strpos($event, 'login') !== FALSE) {
370         $eventIDcode = 110122;
371         $eventIDdisplayName = 'Login';
372     }
373     else if (strpos($event, 'logout') !== FALSE) {
374         $eventIDcode = 110123;
375         $eventIDdisplayName = 'Logout';
376     }
377     else if (strpos($event, 'scheduling') !== FALSE) {
378         $eventIDcode = 110111;
379         $eventIDdisplayName = 'Patient Care Assignment';
380     }
381     else if (strpos($event, 'security-administration') !== FALSE) {
382         $eventIDcode = 110129;
383         $eventIDdisplayName = 'Security Administration';
384     }
389     /* Variables used in ActiveParticipant section, which identifies
390      * the IP address and application of the source and destination.
391      */
392     $srcUserID = $_SERVER['SERVER_NAME'] . '|OpenEMR';
393     $srcNetwork = $_SERVER['SERVER_ADDR'];
394     $destUserID = $GLOBALS['atna_audit_host'];
395     $destNetwork = $GLOBALS['atna_audit_host'];
397     $userID = $user;
398     $userTypeCode = 1;
399     $userRole = 6;
400     $userCode = 11;
401     $userDisplayName = 'User Identifier';
403     $patientID = "";
404     $patientTypeCode = "";
405     $patientRole = "";
406     $patientCode = "";
407     $patientDisplayName = "";
409     if ($eventIDdisplayName == 'Patient Record') {
410         $patientID = $patient_id;
411         $pattientTypeCode = 1;
412         $patientRole = 1;
413         $patientCode = 2;
414         $patientDisplayName = 'Patient Number';
415     }
417     /* Construct the XML audit message, and save to $msg */
418     $msg =  '<?xml version="1.0" encoding="ASCII"?>';
419     $msg .= '<AuditMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
420     $msg .= 'xsi:noNamespaceSchemaLocation="healthcare-security-audit.xsd">';
422     /* Indicate the event code, text name, read/write type, and date/time */
423     $msg .= "<EventIdentification EventActionCode=\"$eventActionCode\" ";
424     $msg .= "EventDateTime=\"$eventDateTime\" ";
425     $msg .= "EventOutcomeIndicator=\"$eventOutcome\">";
426     $msg .= "<EventID code=\"eventIDcode\" displayName=\"$eventIDdisplayName\" ";
427     $msg .= "codeSystemName=\"DCM\" />";
428     $msg .= "</EventIdentification>";
430     /* Indicate the IP address and application of the source and destination */
431     $msg .= "<ActiveParticipant UserID=\"$srcUserID\" UserIsRequestor=\"true\" ";
432     $msg .= "NetworkAccessPointID=\"$srcNetwork\" NetworkAccessPointTypeCode=\"2\" >";
433     $msg .= "<RoleIDCode code=\"110153\" displayName=\"Source\" codeSystemName=\"DCM\" />";
434     $msg .= "</ActiveParticipant>";
435     $msg .= "<ActiveParticipant UserID=\"$destUserID\" UserIsRequestor=\"false\" ";
436     $msg .= "NetworkAccessPointID=\"$destNetwork\" NetworkAccessPointTypeCode=\"2\" >";
437     $msg .= "<RoleIDCode code=\"110152\" displayName=\"Destination\" codeSystemName=\"DCM\" />";
438     $msg .= "</ActiveParticipant>";
440     $msg .= "<AuditSourceIdentification AuditSourceID=\"$srcUserID\" />";
442     /* Indicate the username who generated this audit record */
443     $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$user\" ";
444     $msg .= "ParticipantObjectTypeCode=\"1\" ";
445     $msg .= "ParticipantObjectTypeCodeRole=\"6\" >";
446     $msg .= "<ParticipantObjectIDTypeCode code=\"11\" ";
447     $msg .= "displayName=\"User Identifier\" ";
448     $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
450     if ($eventIDdisplayName == 'Patient Record' && $patient_id != 0) {
451         $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$patient_id\" ";
452         $msg .= "ParticipantObjectTypeCode=\"1\" ";
453         $msg .= "ParticipantObjectTypeCodeRole=\"1\" >";
454         $msg .= "<ParticipantObjectIDTypeCode code=\"2\" ";
455         $msg .= "displayName=\"Patient Number\" ";
456         $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
457     }
458     $msg .= "</AuditMessage>";
460     /* Add the syslog header */
461     $date_obj = new DateTime($date);
462     $datestr= $date_obj->format(DATE_ATOM);
463     $msg = "<13> " . $datestr . " " . $_SERVER['SERVER_NAME'] . " " . $msg;
464     return $msg;
468 /* Create a TLS (SSLv3) connection to the given host/port.
469  * $localcert is the path to a PEM file with a client certificate and private key.
470  * $cafile is the path to the CA certificate file, for
471  *  authenticating the remote machine's certificate.
472  * If $cafile is "", the remote machine's certificate is not verified.
473  * If $localcert is "", we don't pass a client certificate in the connection.
475  * Return a stream resource that can be used with fwrite(), fread(), etc.
476  * Returns FALSE on error.
477  */
478 function create_tls_conn($host, $port, $localcert, $cafile) {
479     $sslopts = array();
480     if ($cafile !== null && $cafile != "") {
481         $sslopts['cafile'] = $cafile;
482         $sslopts['verify_peer'] = TRUE;
483         $sslopts['verify_depth'] = 10;
484     }
485     if ($localcert !== null && $localcert != "") {
486         $sslopts['local_cert'] = $localcert;
487     }
488     $opts = array('tls' => $sslopts, 'ssl' => $sslopts);
489     $ctx = stream_context_create($opts);
490     $timeout = 60;
491     $flags = STREAM_CLIENT_CONNECT;
493     $olderr = error_reporting(0);
494     $conn = stream_socket_client('tls://' . $host . ":" . $port, $errno, $errstr,
495                                  $timeout, $flags, $ctx);
496     error_reporting($olderr);
497     return $conn;
501 /* This function is used to send audit records to an Audit Repository Server,
502  * as described in the Audit Trail and Node Authentication (ATNA) standard.
503  * Given the fields in a single audit record:
504  * - Create an XML audit message according to RFC 3881, including the RFC5425 syslog header.
505  * - Create a TLS connection that performs bi-directions certificate authentication,
506  *   according to RFC 5425.
507  * - Send the XML message on the TLS connection.
508  */
509 function send_atna_audit_msg($user, $group, $event, $patient_id, $outcome, $comments)
511     /* If no ATNA repository server is configured, return */
512     if (empty($GLOBALS['atna_audit_host']) || empty($GLOBALS['enable_atna_audit']) ) {
513         return;
514     }
516     $host = $GLOBALS['atna_audit_host'];
517     $port = $GLOBALS['atna_audit_port'];
518     $localcert = $GLOBALS['atna_audit_localcert'];
519     $cacert = $GLOBALS['atna_audit_cacert'];
520     $conn = create_tls_conn($host, $port, $localcert, $cacert);
521     if ($conn !== FALSE) {
522         $msg = create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments);
523         $len = strlen($msg);
524         fwrite($conn, $msg);
525         fclose($conn);
526     }
530 /* Add an entry into the audit log table, indicating that an
531  * SQL query was performed. $outcome is true if the statement
532  * successfully completed.  Determine the event type based on
533  * the tables present in the SQL query.
534  */
535 function auditSQLEvent($statement, $outcome, $binds=NULL)
538     $user =  isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
539         /* Don't log anything if the audit logging is not enabled. Exception for "emergency" users */
540    if (!isset($GLOBALS['enable_auditlog']) || !($GLOBALS['enable_auditlog']))
541    {
542             if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
543             return;
544    }
547    $statement = trim($statement);
549     /* Don't audit SQL statements done to the audit log,
550      * or we'll have an infinite loop.
551      */
552     if ((stripos($statement, "insert into log") !== FALSE) ||
553         (stripos($statement, "FROM log ") !== FALSE) ) {
554         return;
555     }
557     $group = isset($_SESSION['authGroup']) ?  $_SESSION['authGroup'] : "";
558     $comments = $statement;
560     $processed_binds = "";
561     if (is_array($binds)) {
562         // Need to include the binded variable elements in the logging
563         $first_loop=true;
564         foreach ($binds as $value_bind) {
565             if ($first_loop) {
566                 //no comma
567                 $processed_binds .= "'" . add_escape_custom($value_bind) . "'";
568                 $first_loop=false;
569             }
570             else {
571                 //add a comma
572                 $processed_binds .= ",'" . add_escape_custom($value_bind) . "'";
573             }
574         }
575         if (!empty($processed_binds)) {
576             $processed_binds = "(" . $processed_binds . ")";
577             $comments .= " " . $processed_binds;
578         }
579     }
581     $success = 1;
582     $checksum = "";
583     if ($outcome === FALSE) {
584         $success = 0;
585     }
586     if ($outcome !== FALSE) {
587         // Should use the $statement rather than the processed
588         // variables, which includes the binded stuff. If do
589         // indeed need the binded values, then will need
590         // to include this as a separate array.
592         //error_log("STATEMENT: ".$statement,0);
593         //error_log("BINDS: ".$processed_binds,0);
594         $checksum = sql_checksum_of_modified_row($statement);
595         //error_log("CHECKSUM: ".$checksum,0);
596     }
597     /* Determine the query type (select, update, insert, delete) */
598     $querytype = "select";
599     $querytypes = array("select", "update", "insert", "delete","replace");
600     foreach ($querytypes as $qtype) {
601         if (stripos($statement, $qtype) === 0) {
602             $querytype = $qtype;
603         }
604     }
606     /* Determine the audit event based on the database tables */
607     $event = "other";
608     $category = "other";
609     $tables = array("billing" => "patient-record",
610                     "claims" => "patient-record",
611                     "employer_data" => "patient-record",
612                     "forms" => "patient-record",
613                     "form_encounter" => "patient-record",
614                     "form_dictation" => "patient-record",
615                     "form_misc_billing_options" => "patient-record",
616                     "form_reviewofs" => "patient-record",
617                     "form_ros" => "patient-record",
618                     "form_soap" => "patient-record",
619                     "form_vitals" => "patient-record",
620                     "history_data" => "patient-record",
621                     "immunizations" => "patient-record",
622                     "insurance_data" => "patient-record",
623                     "issue_encounter" => "patient-record",
624                     "lists" => "patient-record",
625                     "patient_data" => "patient-record",
626                     "payments" => "patient-record",
627                     "pnotes" => "patient-record",
628                     "onotes" => "patient-record",
629                     "prescriptions" => "order",
630                     "transactions" => "patient-record",
631                                         "amendments" => "patient-record",
632                                         "amendments_history" => "patient-record",
633                     "facility" => "security-administration",
634                     "pharmacies" => "security-administration",
635                     "addresses" => "security-administration",
636                     "phone_numbers" => "security-administration",
637                     "x12_partners" => "security-administration",
638                     "insurance_companies" => "security-administration",
639                     "codes" => "security-administration",
640                     "registry" => "security-administration",
641                     "users" => "security-administration",
642                     "groups" => "security-administration",
643                     "openemr_postcalendar_events" => "scheduling",
644                                 "openemr_postcalendar_categories" => "security-administration",
645                                 "openemr_postcalendar_limits" => "security-administration",
646                                 "openemr_postcalendar_topics" => "security-administration",
647                                 "gacl_acl" => "security-administration",
648                                 "gacl_acl_sections" => "security-administration",
649                                 "gacl_acl_seq" => "security-administration",
650                                 "gacl_aco" => "security-administration",
651                                 "gacl_aco_map" => "security-administration",
652                                 "gacl_aco_sections" => "security-administration",
653                                 "gacl_aco_sections_seq" => "security-administration",
654                                 "gacl_aco_seq" => "security-administration",
655                                 "gacl_aro" => "security-administration",
656                                 "gacl_aro_groups" => "security-administration",
657                                 "gacl_aro_groups_id_seq" => "security-administration",
658                                 "gacl_aro_groups_map" => "security-administration",
659                                 "gacl_aro_map" => "security-administration",
660                                 "gacl_aro_sections" => "security-administration",
661                                 "gacl_aro_sections_seq" => "security-administration",
662                                 "gacl_aro_seq" => "security-administration",
663                                 "gacl_axo" => "security-administration",
664                                 "gacl_axo_groups" => "security-administration",
665                                 "gacl_axo_groups_map" => "security-administration",
666                                 "gacl_axo_map" => "security-administration",
667                                 "gacl_axo_sections" => "security-administration",
668                                 "gacl_groups_aro_map" => "security-administration",
669                                 "gacl_groups_axo_map" => "security-administration",
670                                 "gacl_phpgacl" => "security-administration",
671                                 "procedure_order" => "lab-order",
672                             "procedure_order_code" => "lab-order",
673                                 "procedure_report" => "lab-results",
674                                 "procedure_result" => "lab-results" 
675                   );
677     /* When searching for table names, truncate the SQL statement,
678      * removing any WHERE, SET, or VALUE clauses.
679      */
680         $truncated_sql = $statement;
681         $truncated_sql = str_replace("\n", " ", $truncated_sql);
682         if ($querytype == "select") {
683         $startwhere = stripos($truncated_sql, " where ");
684         if ($startwhere > 0) {
685         $truncated_sql = substr($truncated_sql, 0, $startwhere);
686     }
688         else {
689      $startparen = stripos($truncated_sql, "(" );
690      $startset = stripos($truncated_sql, " set ");
691      $startvalues = stripos($truncated_sql, " values ");
693         if ($startparen > 0) {
694             $truncated_sql = substr($truncated_sql, 0, $startparen);
695         }
696         if ($startvalues > 0) {
697             $truncated_sql = substr($truncated_sql, 0, $startvalues);
698         }
699         if ($startset > 0) {
700             $truncated_sql = substr($truncated_sql, 0, $startset);
701         }
702     }
703     foreach ($tables as $table => $value) {
704         if (strpos($truncated_sql, $table) !== FALSE) {
705             $event = $value;
706             $category = eventCategoryFinder($comments, $event,$table);
707             break;
708         }
709       else if (strpos($truncated_sql, "form_") !== FALSE) {
710             $event = "patient-record";
711             $category = eventCategoryFinder($comments, $event,$table);
712              break;
713         }
714     }
716     /* Avoid filling the audit log with trivial SELECT statements.
717      * Skip SELECTs from unknown tables.
718      * Skip SELECT count() statements.
719      * Skip the SELECT made by the authCheckSession() function.
720      */
721     if ($querytype == "select") {
722         if ($event == "other")
723             return;
724         if (stripos($statement, "SELECT count(" ) === 0)
725             return;
726         if (stripos($statement, "select username, password from users") === 0)
727             return;
728     }
731     /* If the event is a patient-record, then note the patient id */
732     $pid = 0;
733     if ($event == "patient-record") {
734         if (array_key_exists('pid', $_SESSION) && $_SESSION['pid'] != '') {
735             $pid = $_SESSION['pid'];
736         }
737     }
739     /* If query events are not enabled, don't log them */
740     if (($querytype == "select") && !(array_key_exists('audit_events_query', $GLOBALS) && $GLOBALS['audit_events_query']))
741     {
742        if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
743        return;
744     }
746     if (!($GLOBALS["audit_events_${event}"]))
747     {
748         if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
749         return;
750     }
753     $event = $event . "-" . $querytype;
755     $adodb = $GLOBALS['adodb']['db'];
757     // ViSolve : Don't log sequences - to avoid the affect due to GenID calls
758     if (strpos($comments, "sequences") !== FALSE) return;
760         $encrypt_comment = 'No';
761         //July 1, 2014: Ensoftek: Check and encrypt audit logging
762         if (array_key_exists('enable_auditlog_encryption', $GLOBALS) && $GLOBALS["enable_auditlog_encryption"]) {
763                 $comments =  aes256Encrypt($comments);
764                 $encrypt_comment = 'Yes';
765         }
767         $current_datetime = date("Y-m-d H:i:s");
768     $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
769     $sql = "insert into log (date, event,category, user, groupname, comments, patient_id, success, checksum,crt_user) " .
770          "values ( ".
771                  $adodb->qstr($current_datetime). ", ".
772          $adodb->qstr($event) . ", " .
773          $adodb->qstr($category) . ", " .
774          $adodb->qstr($user) . "," .
775          $adodb->qstr($group) . "," .
776          $adodb->qstr($comments) . "," .
777          $adodb->qstr($pid) . "," .
778          $adodb->qstr($success) . "," .
779          $adodb->qstr($checksum) . "," .
780          $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
781         sqlInsertClean_audit($sql);
783         $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
784         $checksumGenerate = '';
785         //July 1, 2014: Ensoftek: Record the encryption checksum in a secondary table(log_comment_encrypt)
786         if ($querytype == 'update') {
787                 $concatLogColumns = $current_datetime.$event.$user.$group.$comments.$pid.$success.$checksum.$SSL_CLIENT_S_DN_CN;
788                 $checksumGenerate = sha1($concatLogColumns);
789         }
790         $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
791                                          " VALUES ( ".
792                                           $adodb->qstr($last_log_id) . "," .
793                                           $adodb->qstr($encrypt_comment) . "," .
794                                           $adodb->qstr($checksumGenerate) .")";
795         sqlInsertClean_audit($encryptLogQry);
797     send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
798     //return $ret;
801 // May-29-2014: Ensoftek: For Auditable events and tamper-resistance (MU2)
802 // Insert Audit Logging Status into the LOG table.
803 function auditSQLAuditTamper($enable)
805     $user =  isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
806     $group = isset($_SESSION['authGroup']) ?  $_SESSION['authGroup'] : "";
807     $pid = 0;
808     $checksum = "";
809     $success = 1;
810         $event = "security-administration" . "-" . "insert";
813     $adodb = $GLOBALS['adodb']['db'];
815         if ($enable == "1")
816         {
817                 $comments = "Audit Logging Enabled.";
818         }
819         else
820         {
821                 $comments = "Audit Logging Disabled.";
822         }
824     $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
825     $sql = "insert into log (date, event, user, groupname, comments, patient_id, success, checksum,crt_user) " .
826          "values ( NOW(), " .
827          $adodb->qstr($event) . ", " .
828          $adodb->qstr($user) . "," .
829          $adodb->qstr($group) . "," .
830          $adodb->qstr($comments) . "," .
831          $adodb->qstr($pid) . "," .
832          $adodb->qstr($success) . "," .
833          $adodb->qstr($checksum) . "," .
834          $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
836     sqlInsertClean_audit($sql);
837     send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
841  * Record the patient disclosures.
842  * @param $dates    - The date when the disclosures are sent to the thrid party.
843  * @param $event    - The type of the disclosure.
844  * @param $pid      - The id of the patient for whom the disclosures are recorded.
845  * @param $comment  - The recipient name and description of the disclosure.
846  * @uname           - The username who is recording the disclosure.
847  */
848 function recordDisclosure($dates,$event,$pid,$recipient,$description,$user)
850         $adodb = $GLOBALS['adodb']['db'];
851         $crt_user= $_SERVER['SSL_CLIENT_S_DN_CN'];
852         $groupname=$_SESSION['authProvider'];
853         $success=1;
854         $sql = "insert into extended_log ( date, event, user, recipient, patient_id, description) " .
855             "values (" . $adodb->qstr($dates) . "," . $adodb->qstr($event) . "," . $adodb->qstr($user) .
856             "," . $adodb->qstr($recipient) . ",".
857             $adodb->qstr($pid) ."," .
858             $adodb->qstr($description) .")";
859         $ret = sqlInsertClean_audit($sql);
862  * Edit the disclosures that is recorded.
863  * @param $dates  - The date when the disclosures are sent to the thrid party.
864  * @param $event  - The type of the disclosure.
865  * param $comment - The recipient and the description of the disclosure are appended.
866  * $logeventid    - The id of the record which is to be edited.
867  */
868 function updateRecordedDisclosure($dates,$event,$recipient,$description,$disclosure_id)
870          $adodb = $GLOBALS['adodb']['db'];
871          $sql="update extended_log set
872                 event=" . $adodb->qstr($event) . ",
873                 date=" .  $adodb->qstr($dates) . ",
874                 recipient=" . $adodb->qstr($recipient) . ",
875                 description=" . $adodb->qstr($description) . "
876                 where id=" . $adodb->qstr($disclosure_id) . "";
877           $ret = sqlInsertClean_audit($sql);
880  * Delete the disclosures that is recorded.
881  * $deleteid - The id of the record which is to be deleted.
882  */
883 function deleteDisclosure($deletelid)
885         $sql="delete from extended_log where id='" . add_escape_custom($deletelid) . "'";
886         $ret = sqlInsertClean_audit($sql);
889 //July 1, 2014: Ensoftek: Function to AES256 encrypt a given string
890 function aes256Encrypt($sValue){
891         $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
892     return rtrim(
893         base64_encode(
894             mcrypt_encrypt(
895                 MCRYPT_RIJNDAEL_256,
896                 $sSecretKey, $sValue,
897                 MCRYPT_MODE_ECB,
898                 mcrypt_create_iv(
899                     mcrypt_get_iv_size(
900                         MCRYPT_RIJNDAEL_256,
901                         MCRYPT_MODE_ECB
902                     ),
903                     MCRYPT_RAND)
904                 )
905             ), "\0"
906         );
909 //July 1, 2014: Ensoftek: Function to AES256 decrypt a given string
910 function aes256Decrypt($sValue){
911         $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
912     return rtrim(
913         mcrypt_decrypt(
914             MCRYPT_RIJNDAEL_256,
915             $sSecretKey,
916             base64_decode($sValue),
917             MCRYPT_MODE_ECB,
918             mcrypt_create_iv(
919                 mcrypt_get_iv_size(
920                     MCRYPT_RIJNDAEL_256,
921                     MCRYPT_MODE_ECB
922                 ),
923                 MCRYPT_RAND
924             )
925         ), "\0"
926     );
929 //July 1, 2014: Ensoftek: Utility function to get data from table(log_comment_encrypt)
930 function logCommentEncryptData($log_id){
931         $encryptRow = array();
932         $logRes = sqlStatement("SELECT * FROM log_comment_encrypt WHERE log_id=?", array($log_id));
933         while($logRow = sqlFetchArray($logRes)){
934                 $encryptRow['encrypt'] = $logRow['encrypt'];
935                 $encryptRow['checksum'] = $logRow['checksum'];
936         }
937         return $encryptRow;
941  * Function used to determine category of the event
942  * 
943  */
944 function eventCategoryFinder($sql,$event,$table){
945         if($event == 'delete'){
946                 if(strpos($sql, "lists:") === 0){
947                         $fieldValues    = explode("'",$sql);
948                         if(in_array('medical_problem',$fieldValues) === TRUE) return 'Problem List';
949                         else if(in_array('medication',$fieldValues) === TRUE) return 'Medication';
950                         else if(in_array('allergy', $fieldValues) === TRUE) return 'Allergy';
951                 }
952         }
953         if($table == 'lists' || $table == 'lists_touch'){
954                 $trimSQL                = stristr($sql, $table);
955                 $fieldValues    = explode("'",$trimSQL);
956                 if(in_array('medical_problem',$fieldValues) === TRUE) return 'Problem List';
957                 else if(in_array('medication',$fieldValues) === TRUE) return 'Medication';
958                 else if(in_array('allergy', $fieldValues) === TRUE) return 'Allergy';
959         }
960         else if($table == 'immunizations') return "Immunization";
961         else if($table == 'form_vitals') return "Vitals";
962         else if($table == 'history_data') return "Social and Family History";
963         else if($table == 'forms' || $table == 'form_encounter' || strpos($table,'form_') === 0) return "Encounter Form";
964         else if($table == 'insurance_data') return "Patient Insurance";
965         else if($table == 'patient_data' || $table == 'employer_data') return "Patient Demographics";
966         else if($table == 'payments' || $table == "billing" || $table == "claims") return "Billing";
967         else if($table == 'pnotes') return "Clinical Mail";
968         else if($table == 'prescriptions') return "Medication";
969         else if($table == 'transactions'){
970                 $trimSQL                = stristr($sql, "transactions");
971                 $fieldValues    = explode("'",$trimSQL);
972                 if(in_array("LBTref", $fieldValues)) return "Referral";
973                 else return $event;
974         }
975         else if($table == 'amendments' || $table == 'amendments_history') return "Amendments";
976         else if($table == 'openemr_postcalendar_events') return "Scheduling";
977         else if($table == 'procedure_order' || $table == 'procedure_order_code') return "Lab Order";
978         else if($table == 'procedure_report' || $table == 'procedure_result') return "Lab Result";
979         else if($event == 'security-administration') return "Security";
980         
981         return $event;