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