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