complete a9 (#4976)
[openemr.git] / interface / patient_file / summary / demographics.php
blobd59244b6a1af6c079027bf25c661e0af09b6b8e9
1 <?php
3 /**
5 * Patient summary screen.
7 * @package OpenEMR
8 * @link http://www.open-emr.org
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Sharon Cohen <sharonco@matrix.co.il>
11 * @author Stephen Waite <stephen.waite@cmsvt.com>
12 * @author Ranganath Pathak <pathak@scrs1.org>
13 * @author Tyler Wrenn <tyler@tylerwrenn.com>
14 * @author Robert Down <robertdown@live.com>
15 * @copyright Copyright (c) 2017-2020 Brady Miller <brady.g.miller@gmail.com>
16 * @copyright Copyright (c) 2017 Sharon Cohen <sharonco@matrix.co.il>
17 * @copyright Copyright (c) 2018-2020 Stephen Waite <stephen.waite@cmsvt.com>
18 * @copyright Copyright (c) 2018 Ranganath Pathak <pathak@scrs1.org>
19 * @copyright Copyright (c) 2020 Tyler Wrenn <tyler@tylerwrenn.com>
20 * @copyright Copyright (c) 2021-2022 Robert Down <robertdown@live.com
21 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
24 require_once("../../globals.php");
25 require_once("$srcdir/patient.inc");
26 require_once("$srcdir/options.inc.php");
27 require_once("../history/history.inc.php");
28 require_once("$srcdir/clinical_rules.php");
29 require_once("$srcdir/group.inc");
30 require_once(__DIR__ . "/../../../library/appointments.inc.php");
32 use OpenEMR\Billing\EDI270;
33 use OpenEMR\Common\Acl\AclMain;
34 use OpenEMR\Common\Csrf\CsrfUtils;
35 use OpenEMR\Common\Session\SessionUtil;
36 use OpenEMR\Common\Twig\TwigContainer;
37 use OpenEMR\Core\Header;
38 use OpenEMR\Events\Patient\Summary\Card\RenderEvent as CardRenderEvent;
39 use OpenEMR\Events\Patient\Summary\Card\RenderModel;
40 use OpenEMR\Events\PatientDemographics\ViewEvent;
41 use OpenEMR\Events\PatientDemographics\RenderEvent;
42 use OpenEMR\FHIR\SMART\SmartLaunchController;
43 use OpenEMR\Menu\PatientMenuRole;
44 use OpenEMR\OeUI\OemrUI;
45 use OpenEMR\Reminder\BirthdayReminder;
46 use Symfony\Component\EventDispatcher\EventDispatcher;
48 $twig = new TwigContainer(null, $GLOBALS['kernel']);
50 // Set session for pid (via setpid). Also set session for encounter (if applicable)
51 if (isset($_GET['set_pid'])) {
52 require_once("$srcdir/pid.inc");
53 setpid($_GET['set_pid']);
54 if (isset($_GET['set_encounterid']) && ((int)$_GET['set_encounterid'] > 0)) {
55 $encounter = (int)$_GET['set_encounterid'];
56 SessionUtil::setSession('encounter', $encounter);
60 // Note: it would eventually be a good idea to move this into
61 // it's own module that people can remove / add if they don't
62 // want smart support in their system.
63 $smartLaunchController = new SMARTLaunchController($GLOBALS["kernel"]->getEventDispatcher());
64 $smartLaunchController->registerContextEvents();
66 /**
67 * @var EventDispatcher
69 $ed = $GLOBALS['kernel']->getEventDispatcher();
71 $active_reminders = false;
72 $all_allergy_alerts = false;
73 if ($GLOBALS['enable_cdr']) {
74 //CDR Engine stuff
75 if ($GLOBALS['enable_allergy_check'] && $GLOBALS['enable_alert_log']) {
76 //Check for new allergies conflicts and throw popup if any exist(note need alert logging to support this)
77 $new_allergy_alerts = allergy_conflict($pid, 'new', $_SESSION['authUser']);
78 if (!empty($new_allergy_alerts)) {
79 $pod_warnings = '';
80 foreach ($new_allergy_alerts as $new_allergy_alert) {
81 $pod_warnings .= js_escape($new_allergy_alert) . ' + "\n"';
83 $allergyWarningMessage = '<script>alert(' . xlj('WARNING - FOLLOWING ACTIVE MEDICATIONS ARE ALLERGIES') . ' + "\n" + ' . $pod_warnings . ')</script>';
87 if ((empty($_SESSION['alert_notify_pid']) || ($_SESSION['alert_notify_pid'] != $pid)) && isset($_GET['set_pid']) && $GLOBALS['enable_cdr_crp']) {
88 // showing a new patient, so check for active reminders and allergy conflicts, which use in active reminder popup
89 $active_reminders = active_alert_summary($pid, "reminders-due", '', 'default', $_SESSION['authUser'], true);
90 if ($GLOBALS['enable_allergy_check']) {
91 $all_allergy_alerts = allergy_conflict($pid, 'all', $_SESSION['authUser'], true);
94 SessionUtil::setSession('alert_notify_pid', $pid);
95 // can not output html until after above setSession call
96 if (!empty($allergyWarningMessage)) {
97 echo $allergyWarningMessage;
100 //Check to see is only one insurance is allowed
101 if ($GLOBALS['insurance_only_one']) {
102 $insurance_array = array('primary');
103 } else {
104 $insurance_array = array('primary', 'secondary', 'tertiary');
107 function print_as_money($money)
109 preg_match("/(\d*)\.?(\d*)/", $money, $moneymatches);
110 $tmp = wordwrap(strrev($moneymatches[1]), 3, ",", 1);
111 $ccheck = strrev($tmp);
112 if ($ccheck[0] == ",") {
113 $tmp = substr($ccheck, 1, strlen($ccheck) - 1);
116 if ($moneymatches[2] != "") {
117 return "$ " . strrev($tmp) . "." . $moneymatches[2];
118 } else {
119 return "$ " . strrev($tmp);
123 // get an array from Photos category
124 function pic_array($pid, $picture_directory)
126 $pics = array();
127 $sql_query = "select documents.id from documents join categories_to_documents " .
128 "on documents.id = categories_to_documents.document_id " .
129 "join categories on categories.id = categories_to_documents.category_id " .
130 "where categories.name like ? and documents.foreign_id = ? and documents.deleted = 0";
131 if ($query = sqlStatement($sql_query, array($picture_directory, $pid))) {
132 while ($results = sqlFetchArray($query)) {
133 array_push($pics, $results['id']);
137 return ($pics);
140 // Get the document ID of the first document in a specific catg.
141 function get_document_by_catg($pid, $doc_catg)
143 $result = array();
145 if ($pid and $doc_catg) {
146 $result = sqlQuery("SELECT d.id, d.date, d.url
147 FROM documents AS d, categories_to_documents AS cd, categories AS c
148 WHERE d.foreign_id = ?
149 AND cd.document_id = d.id
150 AND c.id = cd.category_id
151 AND c.name LIKE ?
152 ORDER BY d.date DESC LIMIT 1", array($pid, $doc_catg));
155 return ($result['id'] ?? false);
158 function portalAuthorized($pid)
160 if (!$GLOBALS['portal_onsite_two_enable'] && !$GLOBALS['portal_onsite_two_address']) {
161 return false;
164 $return = [
165 'allowed' => false,
166 'created' => false,
169 $portalStatus = sqlQuery("SELECT allow_patient_portal FROM patient_data WHERE pid = ?", [$pid]);
170 if ($portalStatus['allow_patient_portal'] == 'YES') {
171 $return['allowed'] = true;
172 $portalLogin = sqlQuery("SELECT pid FROM `patient_access_onsite` WHERE `pid`=?", [$pid]);
173 if ($portalLogin) {
174 $return['created'] = true;
176 return $return;
180 function deceasedDays($days_deceased)
182 $deceased_days = intval($days_deceased['days_deceased'] ?? '');
183 if ($deceased_days == 0) {
184 $num_of_days = xl("Today");
185 } elseif ($deceased_days == 1) {
186 $num_of_days = $deceased_days . " " . xl("day ago");
187 } elseif ($deceased_days > 1 && $deceased_days < 90) {
188 $num_of_days = $deceased_days . " " . xl("days ago");
189 } elseif ($deceased_days >= 90 && $deceased_days < 731) {
190 $num_of_days = "~" . round($deceased_days / 30) . " " . xl("months ago"); // function intdiv available only in php7
191 } elseif ($deceased_days >= 731) {
192 $num_of_days = xl("More than") . " " . round($deceased_days / 365) . " " . xl("years ago");
195 if (strlen($days_deceased['date_deceased'] ?? '') > 10 && $GLOBALS['date_display_format'] < 1) {
196 $deceased_date = substr($days_deceased['date_deceased'], 0, 10);
197 } else {
198 $deceased_date = oeFormatShortDate($days_deceased['date_deceased'] ?? '');
201 return xlt("Deceased") . " - " . text($deceased_date) . " (" . text($num_of_days) . ")";
204 $deceased = is_patient_deceased($pid);
207 // Display image in 'widget style'
208 function image_widget($doc_id, $doc_catg)
210 global $pid, $web_root;
211 $docobj = new Document($doc_id);
212 $image_file = $docobj->get_url_file();
213 $image_file_name = $docobj->get_name();
214 $image_width = $GLOBALS['generate_doc_thumb'] == 1 ? '' : 'width=100';
215 $extension = substr($image_file_name, strrpos($image_file_name, "."));
216 $viewable_types = array('.png', '.jpg', '.jpeg', '.png', '.bmp', '.PNG', '.JPG', '.JPEG', '.PNG', '.BMP');
217 if (in_array($extension, $viewable_types)) { // extension matches list
218 $to_url = "<td> <a href = '$web_root" .
219 "/controller.php?document&retrieve&patient_id=" . attr_url($pid) . "&document_id=" . attr_url($doc_id) . "&as_file=false&original_file=true&disable_exit=false&show_original=true'" .
220 " onclick='top.restoreSession();' class='image_modal'>" .
221 " <img src = '$web_root" .
222 "/controller.php?document&retrieve&patient_id=" . attr_url($pid) . "&document_id=" . attr_url($doc_id) . "&as_file=false'" .
223 " $image_width alt='" . attr($doc_catg) . ":" . attr($image_file) . "'> </a> </td> <td class='align-middle'>" .
224 text($doc_catg) . '<br />&nbsp;' . text($image_file) .
225 "</td>";
226 } else {
227 $to_url = "<td> <a href='" . $web_root . "/controller.php?document&retrieve" .
228 "&patient_id=" . attr_url($pid) . "&document_id=" . attr_url($doc_id) . "'" .
229 " onclick='top.restoreSession()' class='btn btn-primary btn-sm'>" .
230 "<span>" .
231 xlt("View") . "</a> &nbsp;" .
232 text("$doc_catg - $image_file") .
233 "</span> </td>";
236 echo "<table><tr>";
237 echo $to_url;
238 echo "</tr></table>";
241 // Determine if the Vitals form is in use for this site.
242 $tmp = sqlQuery("SELECT count(*) AS count FROM registry WHERE directory = 'vitals' AND state = 1");
243 $vitals_is_registered = $tmp['count'];
245 // Get patient/employer/insurance information.
247 $result = getPatientData($pid, "*, DATE_FORMAT(DOB,'%Y-%m-%d') as DOB_YMD");
248 $result2 = getEmployerData($pid);
249 $result3 = getInsuranceData($pid, "primary", "copay, provider, DATE_FORMAT(`date`,'%Y-%m-%d') as effdate");
250 $insco_name = "";
251 if (!empty($result3['provider'])) { // Use provider in case there is an ins record w/ unassigned insco
252 $insco_name = getInsuranceProvider($result3['provider']);
255 $arrOeUiSettings = array(
256 'heading_title' => xl('Medical Record Dashboard'),
257 'include_patient_name' => true,
258 'expandable' => false,
259 'expandable_files' => array(), //all file names need suffix _xpd
260 'action' => "", //conceal, reveal, search, reset, link or back
261 'action_title' => "",
262 'action_href' => "", //only for actions - reset, link or back
263 'show_help_icon' => true,
264 'help_file_name' => "medical_dashboard_help.php"
266 $oemr_ui = new OemrUI($arrOeUiSettings);
268 <!DOCTYPE html>
269 <html>
271 <head>
272 <?php
273 Header::setupHeader(['common']);
274 require_once("$srcdir/options.js.php");
276 <script>
277 // Process click on diagnosis for referential cds popup.
278 function referentialCdsClick(codetype, codevalue) {
279 top.restoreSession();
280 // Force a new window instead of iframe to address cross site scripting potential
281 dlgopen('../education.php?type=' + encodeURIComponent(codetype) + '&code=' + encodeURIComponent(codevalue), '_blank', 1024, 750,true);
284 function oldEvt(apptdate, eventid) {
285 let title = <?php echo xlj('Appointments'); ?>;
286 dlgopen('../../main/calendar/add_edit_event.php?date=' + encodeURIComponent(apptdate) + '&eid=' + encodeURIComponent(eventid), '_blank', 800, 500, '', title);
289 function advdirconfigure() {
290 dlgopen('advancedirectives.php', '_blank', 400, 500);
293 function refreshme() {
294 top.restoreSession();
295 location.reload();
298 // Process click on Delete link.
299 function deleteme() { // @todo don't think this is used any longer!!
300 dlgopen('../deleter.php?patient=' + <?php echo js_url($pid); ?> + '&csrf_token_form=' + <?php echo js_url(CsrfUtils::collectCsrfToken()); ?>, '_blank', 500, 450, '', '', {
301 allowResize: false,
302 allowDrag: false,
303 dialogId: 'patdel',
304 type: 'iframe'
306 return false;
309 // Called by the deleteme.php window on a successful delete.
310 function imdeleted() {
311 top.clearPatient();
314 function newEvt() {
315 let title = <?php echo xlj('Appointments'); ?>;
316 let url = '../../main/calendar/add_edit_event.php?patientid=' + <?php echo js_url($pid); ?>;
317 dlgopen(url, '_blank', 800, 500, '', title);
318 return false;
321 function getWeno() {
322 top.restoreSession();
323 location.href = '../../weno/indexrx.php'
326 function toggleIndicator(target, div) {
327 // <i id="show_hide" class="fa fa-lg small fa-eye-slash" title="Click to Hide"></i>
328 $mode = $(target).find(".indicator").text();
329 if ($mode == <?php echo xlj('collapse'); ?>) {
330 $(target).find(".indicator").text(<?php echo xlj('expand'); ?>);
331 $("#" + div).hide();
332 $.post("../../../library/ajax/user_settings.php", {
333 target: div,
334 mode: 0,
335 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
337 } else {
338 $(target).find(".indicator").text(<?php echo xlj('collapse'); ?>);
339 $("#" + div).show();
340 $.post("../../../library/ajax/user_settings.php", {
341 target: div,
342 mode: 1,
343 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
348 // edit prescriptions dialog.
349 // called from stats.php.
351 function editScripts(url) {
352 var AddScript = function() {
353 var __this = $(this);
354 __this.find("#clearButton").css("display", "");
355 __this.find("#backButton").css("display", "");
356 __this.find("#addButton").css("display", "none");
358 var iam = top.frames.editScripts;
359 iam.location.href = '<?php echo $GLOBALS['webroot'] ?>/controller.php?prescription&edit&id=0&pid=' + <?php echo js_url($pid); ?>;
361 var ListScripts = function() {
362 var __this = $(this);
363 __this.find("#clearButton").css("display", "none");
364 __this.find("#backButton").css("display", "none");
365 __this.find("#addButton").css("display", "");
366 var iam = top.frames.editScripts
367 iam.location.href = '<?php echo $GLOBALS['webroot'] ?>/controller.php?prescription&list&id=' + <?php echo js_url($pid); ?>;
370 let title = <?php echo xlj('Prescriptions'); ?>;
371 let w = 960; // for weno width
373 dlgopen(url, 'editScripts', w, 400, '', '', {
374 buttons: [{
375 text: <?php echo xlj('Add'); ?>,
376 close: false,
377 id: 'addButton',
378 class: 'btn-primary btn-sm',
379 click: AddScript
382 text: <?php echo xlj('Clear'); ?>,
383 close: false,
384 id: 'clearButton',
385 style: 'display:none;',
386 class: 'btn-primary btn-sm',
387 click: AddScript
390 text: <?php echo xlj('Back'); ?>,
391 close: false,
392 id: 'backButton',
393 style: 'display:none;',
394 class: 'btn-primary btn-sm',
395 click: ListScripts
398 text: <?php echo xlj('Quit'); ?>,
399 close: true,
400 id: 'doneButton',
401 class: 'btn-secondary btn-sm'
404 onClosed: 'refreshme',
405 allowResize: true,
406 allowDrag: true,
407 dialogId: 'editscripts',
408 type: 'iframe'
413 * async function fetchHtml(...)
415 * @param {*} url
416 * @param {boolean} embedded
417 * @param {boolean} sessionRestore
418 * @returns {text}
420 async function fetchHtml(url, embedded = false, sessionRestore = false) {
421 if (sessionRestore === true) {
422 // restore cookie before fetch.
423 top.restoreSession();
425 let csrf = new FormData;
426 // a security given.
427 csrf.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
428 if (embedded === true) {
429 // special formatting in certain widgets.
430 csrf.append("embeddedScreen", true);
433 const response = await fetch(url, {
434 method: 'POST',
435 credentials: 'same-origin',
436 body: csrf
438 return await response.text();
442 * async function placeHtml(...) will await fetch of html then place in divId.
443 * This function will return a promise for use to init various items regarding
444 * inserted HTML if needed.
445 * If divId does not exist, then will skip.
446 * Example
448 * @param {*} url
449 * @param {string} divId id
450 * @param {boolean} embedded
451 * @param {boolean} sessionRestore
452 * @returns {object} promise
454 async function placeHtml(url, divId, embedded = false, sessionRestore = false) {
455 const contentDiv = document.getElementById(divId);
456 if (contentDiv) {
457 await fetchHtml(url, embedded, sessionRestore).then(fragment => {
458 contentDiv.innerHTML = fragment;
463 if (typeof load_location === 'undefined') {
464 function load_location(location) {
465 top.restoreSession();
466 document.location = location;
470 $(function() {
471 var msg_updation = '';
472 <?php
473 if ($GLOBALS['erx_enable']) {
474 $soap_status = sqlStatement("select soap_import_status,pid from patient_data where pid=? and soap_import_status in ('1','3')", array($pid));
475 while ($row_soapstatus = sqlFetchArray($soap_status)) { ?>
476 top.restoreSession();
477 $.ajax({
478 type: "POST",
479 url: "../../soap_functions/soap_patientfullmedication.php",
480 dataType: "html",
481 data: {
482 patient: <?php echo js_escape($row_soapstatus['pid']); ?>,
484 async: false,
485 success: function(thedata) {
486 //alert(thedata);
487 msg_updation += thedata;
489 error: function() {
490 alert('ajax error');
494 top.restoreSession();
495 $.ajax({
496 type: "POST",
497 url: "../../soap_functions/soap_allergy.php",
498 dataType: "html",
499 data: {
500 patient: <?php echo js_escape($row_soapstatus['pid']); ?>,
502 async: false,
503 success: function(thedata) {
504 //alert(thedata);
505 msg_updation += thedata;
507 error: function() {
508 alert('ajax error');
511 <?php
512 if ($GLOBALS['erx_import_status_message']) { ?>
513 if (msg_updation)
514 alert(msg_updation);
515 <?php
521 // load divs
522 placeHtml("stats.php", "stats_div", true);
523 placeHtml("pnotes_fragment.php", 'pnotes_ps_expand').then(() => {
524 // must be delegated event!
525 $(this).on("click", ".complete_btn", function() {
526 let btn = $(this);
527 let csrf = new FormData;
528 csrf.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
529 fetch("pnotes_fragment.php?docUpdateId=" + encodeURIComponent(btn.attr('data-id')), {
530 method: "POST",
531 credentials: 'same-origin',
532 body: csrf
534 .then(function() {
535 placeHtml("pnotes_fragment.php", 'pnotes_ps_expand');
539 placeHtml("disc_fragment.php", "disclosures_ps_expand");
540 placeHtml("labdata_fragment.php", "labdata_ps_expand");
541 placeHtml("track_anything_fragment.php", "track_anything_ps_expand");
542 <?php if ($vitals_is_registered && AclMain::aclCheckCore('patients', 'med')) { ?>
543 // Initialize the Vitals form if it is registered and user is authorized.
544 placeHtml("vitals_fragment.php", "vitals_ps_expand");
545 <?php } ?>
547 <?php if ($GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_crw']) { ?>
548 placeHtml("clinical_reminders_fragment.php", "clinical_reminders_ps_expand", true, true).then(() => {
549 // (note need to place javascript code here also to get the dynamic link to work)
550 $(".medium_modal").on('click', function(e) {
551 e.preventDefault();
552 e.stopPropagation();
553 dlgopen('', '', 800, 200, '', '', {
554 buttons: [{
555 text: <?php echo xlj('Close'); ?>,
556 close: true,
557 style: 'secondary btn-sm'
559 onClosed: 'refreshme',
560 allowResize: false,
561 allowDrag: true,
562 dialogId: 'demreminder',
563 type: 'iframe',
564 url: $(this).attr('href')
568 <?php } // end crw
571 <?php if ($GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_prw']) { ?>
572 placeHtml("patient_reminders_fragment.php", "patient_reminders_ps_expand", false, true);
573 <?php } // end prw
576 <?php
577 // Initialize for each applicable LBF form.
578 $gfres = sqlStatement("SELECT grp_form_id
579 FROM layout_group_properties
580 WHERE grp_form_id LIKE 'LBF%'
581 AND grp_group_id = ''
582 AND grp_repeats > 0
583 AND grp_activity = 1
584 ORDER BY grp_seq, grp_title");
585 while ($gfrow = sqlFetchArray($gfres)) { ?>
586 $(<?php echo js_escape("#" . $gfrow['grp_form_id'] . "_ps_expand"); ?>).load("lbf_fragment.php?formname=" + <?php echo js_url($gfrow['grp_form_id']); ?>, {
587 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
589 <?php } ?>
590 tabbify();
592 // modal for dialog boxes
593 $(".large_modal").on('click', function(e) {
594 e.preventDefault();
595 e.stopPropagation();
596 dlgopen('', '', 1000, 600, '', '', {
597 buttons: [{
598 text: <?php echo xlj('Close'); ?>,
599 close: true,
600 style: 'secondary btn-sm'
602 allowResize: true,
603 allowDrag: true,
604 dialogId: '',
605 type: 'iframe',
606 url: $(this).attr('href')
610 $(".rx_modal").on('click', function(e) {
611 e.preventDefault();
612 e.stopPropagation();
613 var title = <?php echo xlj('Amendments'); ?>;
614 dlgopen('', 'editAmendments', 800, 300, '', title, {
615 onClosed: 'refreshme',
616 allowResize: true,
617 allowDrag: true,
618 dialogId: '',
619 type: 'iframe',
620 url: $(this).attr('href')
624 // modal for image viewer
625 $(".image_modal").on('click', function(e) {
626 e.preventDefault();
627 e.stopPropagation();
628 dlgopen('', '', 400, 300, '', <?php echo xlj('Patient Images'); ?>, {
629 allowResize: true,
630 allowDrag: true,
631 dialogId: '',
632 type: 'iframe',
633 url: $(this).attr('href')
637 $(".deleter").on('click', function(e) {
638 e.preventDefault();
639 e.stopPropagation();
640 dlgopen('', '', 600, 360, '', '', {
641 buttons: [{
642 text: <?php echo xlj('Close'); ?>,
643 close: true,
644 style: 'secondary btn-sm'
646 //onClosed: 'imdeleted',
647 allowResize: false,
648 allowDrag: false,
649 dialogId: 'patdel',
650 type: 'iframe',
651 url: $(this).attr('href')
655 $(".iframe1").on('click', function(e) {
656 e.preventDefault();
657 e.stopPropagation();
658 dlgopen('', '', 350, 300, '', '', {
659 buttons: [{
660 text: <?php echo xlj('Close'); ?>,
661 close: true,
662 style: 'secondary btn-sm'
664 allowResize: true,
665 allowDrag: true,
666 dialogId: '',
667 type: 'iframe',
668 url: $(this).attr('href')
671 // for patient portal
672 $(".small_modal").on('click', function(e) {
673 e.preventDefault();
674 e.stopPropagation();
675 dlgopen('', '', 380, 400, '', '', {
676 buttons: [{
677 text: <?php echo xlj('Close'); ?>,
678 close: true,
679 style: 'secondary btn-sm'
681 allowResize: true,
682 allowDrag: true,
683 dialogId: '',
684 type: 'iframe',
685 url: $(this).attr('href')
689 function openReminderPopup() {
690 top.restoreSession()
691 dlgopen('', 'reminders', 500, 250, '', '', {
692 buttons: [{
693 text: <?php echo xlj('Close'); ?>,
694 close: true,
695 style: 'secondary btn-sm'
697 allowResize: true,
698 allowDrag: true,
699 dialogId: '',
700 type: 'iframe',
701 url: $("#reminder_popup_link").attr('href')
705 <?php if ($GLOBALS['patient_birthday_alert']) {
706 // To display the birthday alert:
707 // 1. The patient is not deceased
708 // 2. The birthday is today (or in the past depending on global selection)
709 // 3. The notification has not been turned off (or shown depending on global selection) for this year
710 $birthdayAlert = new BirthdayReminder($pid, $_SESSION['authUserID']);
711 if ($birthdayAlert->isDisplayBirthdayAlert()) {
713 // show the active reminder modal
714 dlgopen('', 'bdayreminder', 300, 170, '', false, {
715 allowResize: false,
716 allowDrag: true,
717 dialogId: '',
718 type: 'iframe',
719 url: $("#birthday_popup").attr('href')
722 <?php } elseif ($active_reminders || $all_allergy_alerts) { ?>
723 openReminderPopup();
724 <?php } ?>
725 <?php } elseif ($active_reminders || $all_allergy_alerts) { ?>
726 openReminderPopup();
727 <?php } ?>
729 // $(".card-title").on('click', "button", (e) => {
730 // console.debug("click");
731 // updateUserVisibilitySetting(e);
732 // });
736 * Change the preference to expand/collapse a given card.
738 * For the given e element, find the corresponding card body, determine if it is collapsed
739 * or shown, and then save the state to the user preferences via an async fetch call POST'ing
740 * the updated setting.
742 * @var e element The Button that was clicked to collapse/expand the card
744 async function updateUserVisibilitySetting(e) {
745 const targetID = e.target.getAttribute("data-target");
746 const target = document.querySelector(targetID);
747 const targetStr = targetID.substring(1);
749 let formData = new FormData();
750 formData.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
751 formData.append("target", targetStr);
752 formData.append("mode", (target.classList.contains("show")) ? 0 : 1);
754 const response = await fetch("../../../library/ajax/user_settings.php", {
755 method: "POST",
756 credentials: 'same-origin',
757 body: formData,
760 const update = await response.text();
761 return update;
764 // Update the User's visibility setting when the card header is clicked
765 function cardTitleButtonClickListener() {
766 const buttons = document.querySelectorAll(".card-title button[data-toggle='collapse']");
767 buttons.forEach((b) => {
768 b.addEventListener("click", (e) => {
769 updateUserVisibilitySetting(e);
774 // JavaScript stuff to do when a new patient is set.
776 function setMyPatient() {
777 <?php
778 if (isset($_GET['set_pid'])) {
779 $date_of_death = is_patient_deceased($pid);
780 if (!empty($date_of_death)) {
781 $date_of_death = $date_of_death['date_deceased'];
784 parent.left_nav.setPatient(<?php echo js_escape($result['fname'] . " " . $result['lname']) .
785 "," . js_escape($pid) . "," . js_escape($result['pubpid']) . ",'',";
786 if (empty($date_of_death)) {
787 echo js_escape(" " . xl('DOB') . ": " . oeFormatShortDate($result['DOB_YMD']) . " " . xl('Age') . ": " . getPatientAgeDisplay($result['DOB_YMD']));
788 } else {
789 echo js_escape(" " . xl('DOB') . ": " . oeFormatShortDate($result['DOB_YMD']) . " " . xl('Age at death') . ": " . oeFormatAge($result['DOB_YMD'], $date_of_death));
790 } ?>);
791 var EncounterDateArray = new Array;
792 var CalendarCategoryArray = new Array;
793 var EncounterIdArray = new Array;
794 var Count = 0;
795 <?php
796 //Encounter details are stored to javacript as array.
797 $result4 = sqlStatement("SELECT fe.encounter,fe.date,openemr_postcalendar_categories.pc_catname FROM form_encounter AS fe " .
798 " left join openemr_postcalendar_categories on fe.pc_catid=openemr_postcalendar_categories.pc_catid WHERE fe.pid = ? order by fe.date desc", array($pid));
799 if (sqlNumRows($result4) > 0) {
800 while ($rowresult4 = sqlFetchArray($result4)) { ?>
801 EncounterIdArray[Count] = <?php echo js_escape($rowresult4['encounter']); ?>;
802 EncounterDateArray[Count] = <?php echo js_escape(oeFormatShortDate(date("Y-m-d", strtotime($rowresult4['date'])))); ?>;
803 CalendarCategoryArray[Count] = <?php echo js_escape(xl_appt_category($rowresult4['pc_catname'])); ?>;
804 Count++;
805 <?php
809 parent.left_nav.setPatientEncounter(EncounterIdArray, EncounterDateArray, CalendarCategoryArray);
810 <?php
811 } // end setting new pid
813 parent.left_nav.syncRadios();
814 <?php if ((isset($_GET['set_pid'])) && (isset($_GET['set_encounterid'])) && (intval($_GET['set_encounterid']) > 0)) {
815 $query_result = sqlQuery("SELECT `date` FROM `form_encounter` WHERE `encounter` = ?", array($encounter)); ?>
816 encurl = 'encounter/encounter_top.php?set_encounter=' + <?php echo js_url($encounter); ?> + '&pid=' + <?php echo js_url($pid); ?>;
817 parent.left_nav.setEncounter(<?php echo js_escape(oeFormatShortDate(date("Y-m-d", strtotime($query_result['date'])))); ?>, <?php echo js_escape($encounter); ?>, 'enc');
818 top.restoreSession();
819 parent.left_nav.loadFrame('enc2', 'enc', 'patient_file/' + encurl);
820 <?php } // end setting new encounter id (only if new pid is also set)
824 $(window).on('load', function() {
825 setMyPatient();
828 document.addEventListener("DOMContentLoaded", () => {
829 cardTitleButtonClickListener();
831 </script>
833 <style>
834 .card {
835 box-shadow: 1px 1px 1px hsl(0 0% 0% / .2);
836 border-radius: 0;
839 <?php
840 // This is for layout font size override.
841 $grparr = array();
842 getLayoutProperties('DEM', $grparr, 'grp_size');
843 if (!empty($grparr['']['grp_size'])) {
844 $FONTSIZE = round($grparr['']['grp_size'] * 1.333333);
845 $FONTSIZE = round($FONTSIZE * 0.0625, 2);
848 /* Override font sizes in the theme. */
849 #DEM .groupname {
850 font-size: <?php echo attr($FONTSIZE); ?>rem;
853 #DEM .label {
854 font-size: <?php echo attr($FONTSIZE); ?>rem;
857 #DEM .data {
858 font-size: <?php echo attr($FONTSIZE); ?>rem;
861 #DEM .data td {
862 font-size: <?php echo attr($FONTSIZE); ?>rem;
865 <?php } ?> :root {
866 --white: #fff;
867 --bg: hsl(0 0% 90%);
870 body {
871 background: var(--bg) !important;
874 section {
875 background: var(--white);
876 margin-top: .25em;
877 padding: .25em;
880 .section-header-dynamic {
881 border-bottom: none;
883 </style>
884 <title><?php echo xlt("Dashboard{{patient file}}"); ?></title>
885 </head>
887 <body class="mt-3 patient-demographic bg-light">
889 <?php
890 // Create and fire the patient demographics view event
891 $viewEvent = new ViewEvent($pid);
892 $viewEvent = $GLOBALS["kernel"]->getEventDispatcher()->dispatch(ViewEvent::EVENT_HANDLE, $viewEvent, 10);
893 $thisauth = AclMain::aclCheckCore('patients', 'demo');
895 if (!$thisauth || !$viewEvent->authorized()) {
896 echo $twig->getTwig()->render('core/unauthorized.html.twig', ['pageTitle' => xl("Medical Dashboard")]);
897 exit();
901 <div id="container_div" class="<?php echo $oemr_ui->oeContainer(); ?> mb-2">
902 <a href='../reminder/active_reminder_popup.php' id='reminder_popup_link' style='display: none' onclick='top.restoreSession()'></a>
903 <a href='../birthday_alert/birthday_pop.php?pid=<?php echo attr_url($pid); ?>&user_id=<?php echo attr_url($_SESSION['authUserID']); ?>' id='birthday_popup' style='display: none;' onclick='top.restoreSession()'></a>
904 <?php
906 if ($thisauth) {
907 if ($result['squad'] && !AclMain::aclCheckCore('squads', $result['squad'])) {
908 $thisauth = 0;
912 if ($thisauth) :
913 require_once("$include_root/patient_file/summary/dashboard_header.php");
914 endif;
916 $list_id = "dashboard"; // to indicate nav item is active, count and give correct id
917 // Collect the patient menu then build it
918 $menuPatient = new PatientMenuRole($twig);
919 $menuPatient->displayHorizNavBarMenu();
920 // Get the document ID of the patient ID card if access to it is wanted here.
921 $idcard_doc_id = false;
922 if ($GLOBALS['patient_id_category_name']) {
923 $idcard_doc_id = get_document_by_catg($pid, $GLOBALS['patient_id_category_name']);
926 <div class="main mb-5">
927 <!-- start main content div -->
928 <div class="row">
929 <div class="col-md-8">
930 <?php
932 if ($deceased > 0) :
933 echo $twig->getTwig()->render('patient/partials/deceased.html.twig', [
934 'deceasedDays' => deceasedDays($deceased),
936 endif;
938 if (!$GLOBALS['hide_billing_widget']) :
939 $forceBillingExpandAlways = ($GLOBALS['force_billing_widget_open']) ? true : false;
940 $patientbalance = get_patient_balance($pid, false);
941 $insurancebalance = get_patient_balance($pid, true) - $patientbalance;
942 $totalbalance = $patientbalance + $insurancebalance;
943 $id = "billing_ps_expand";
944 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('billing'));
945 $viewArgs = [
946 'title' => xl('Billing'),
947 'id' => $id,
948 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
949 'hideBtn' => true,
950 'patientBalance' => $patientbalance,
951 'insuranceBalance' => $insurancebalance,
952 'totalBalance' => $totalbalance,
953 'forceAlwaysOpen' => $forceBillingExpandAlways,
954 'prependedInjection' => $dispatchResult->getPrependedInjection(),
955 'appendedInjection' => $dispatchResult->getAppendedInjection(),
958 if (!empty($result['billing_note'])) {
959 $viewArgs['billingNote'] = $result['billing_note'];
962 if (!empty($result3['provider'])) {
963 $viewArgs['provider'] = true;
964 $viewArgs['insName'] = $insco_name;
965 $viewArgs['copay'] = $result3['copay'];
966 $viewArgs['effDate'] = $result3['effdate'];
969 echo $twig->getTwig()->render('patient/card/billing.html.twig', $viewArgs);
970 endif; // End the hide_billing_widget
972 // if anyone wants to render anything before the patient demographic list
973 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(RenderEvent::EVENT_SECTION_LIST_RENDER_BEFORE, new RenderEvent($pid), 10);
975 if (AclMain::aclCheckCore('patients', 'demo')) :
976 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('demographic'));
977 // Render the Demographics box
978 $viewArgs = [
979 'title' => xl("Demographics"),
980 'id' => "demographics_ps_expand",
981 'btnText' => "Edit",
982 'btnLink' => "demographics_full.php",
983 'linkMethod' => "html",
984 'auth' => ACLMain::aclCheckCore('patients', 'demo', '', 'write'),
985 'requireRestore' => (!isset($_SESSION['patient_portal_onsite_two'])) ? true : false,
986 'initiallyCollapsed' => getUserSetting("demographics_ps_expand") == true ? true : false,
987 'tabID' => "DEM",
988 'result' => $result,
989 'result2' => $result2,
990 'prependedInjection' => $dispatchResult->getPrependedInjection(),
991 'appendedInjection' => $dispatchResult->getAppendedInjection(),
993 echo $twig->getTwig()->render('patient/card/tab_base.html.twig', $viewArgs);
995 // Insurance
996 $insArr = [];
997 $insInBinder = '?';
998 for ($y = 1; count($insurance_array) > $y; $y++) {
999 $insInBinder .= ',?';
1001 $sql = "SELECT * FROM insurance_data WHERE pid = ? AND type IN(" . $insInBinder . ") ORDER BY type, date DESC";
1002 $params[] = $pid;
1003 $params = array_merge($params, $insurance_array);
1004 $res = sqlStatement($sql, $params);
1005 $prior_ins_type = '';
1007 while ($row = sqlFetchArray($res)) {
1008 if ($row['provider']) {
1009 // since the query is sorted by DATE DESC can use prior ins type to identify
1010 $row['isOld'] = (strcmp($row['type'], $prior_ins_type) == 0) ? true : false;
1011 $icobj = new InsuranceCompany($row['provider']);
1012 $adobj = $icobj->get_address();
1013 $insco_name = trim($icobj->get_name());
1014 $row['insco'] = [
1015 'name' => trim($icobj->get_name()),
1016 'address' => [
1017 'line1' => $adobj->get_line1(),
1018 'line2' => $adobj->get_line2(),
1019 'city' => $adobj->get_city(),
1020 'state' => $adobj->get_state(),
1021 'postal' => $adobj->get_zip(),
1022 'country' => $adobj->get_country()
1025 $row['policy_type'] = (!empty($row['policy_type'])) ? $policy_types[$row['policy_type']] : false;
1026 $row['dispFromDate'] = $row['date'] ? true : false;
1027 $mname = ($row['subscriber_mname'] != "") ? $row['subscriber_mname'] : "";
1028 $row['subscriber_full_name'] = str_replace("%mname%", $mname, "{$row['subscriber_fname']} %mname% {$row['subscriber_lname']}");
1029 $insArr[] = $row;
1030 $prior_ins_type = $row['type'];
1034 if ($GLOBALS["enable_oa"]) {
1035 if ($_POST['status_update'] === 'true') {
1036 unset($_POST['status_update']);
1037 $showEligibility = true;
1038 $ok = EDI270::requestEligibleTransaction($pid);
1039 if ($ok === true) {
1040 ob_start();
1041 EDI270::showEligibilityInformation($pid, false);
1042 $output = ob_get_contents();
1043 ob_end_clean();
1044 } else {
1045 $output = $ok;
1047 } else {
1048 ob_start();
1049 EDI270::showEligibilityInformation($pid, true);
1050 $output = ob_get_contents();
1051 ob_end_clean();
1053 } else {
1054 ob_start();
1055 EDI270::showEligibilityInformation($pid, true);
1056 $output = ob_get_contents();
1057 ob_end_clean();
1060 $id = "insurance_ps_expand";
1061 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('insurance'));
1062 $viewArgs = [
1063 'title' => xl("Insurance"),
1064 'id' => $id,
1065 'btnText' => "Edit",
1066 'btnLink' => "demographics_full.php",
1067 'linkMethod' => 'html',
1068 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1069 'ins' => $insArr,
1070 'eligibility' => $output,
1071 'enable_oa' => $GLOBALS['enable_oa'],
1072 'auth' => AclMain::aclCheckCore('patients', 'demo', '', 'write'),
1073 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1074 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1077 if (count($insArr) > 0) {
1078 echo $twig->getTwig()->render('patient/card/insurance.html.twig', $viewArgs);
1080 endif; // end if demographics authorized
1082 if (AclMain::aclCheckCore('patients', 'notes')) :
1083 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('note'));
1084 // Notes expand collapse widget
1085 $id = "pnotes_ps_expand";
1086 $viewArgs = [
1087 'title' => xl("Messages"),
1088 'id' => $id,
1089 'btnLabel' => "Edit",
1090 'btnLink' => "pnotes_full.php?form_active=1",
1091 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1092 'linkMethod' => "html",
1093 'bodyClass' => "notab",
1094 'auth' => AclMain::aclCheckCore('patients', 'notes', '', 'write'),
1095 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1096 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1098 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1099 endif; // end if notes authorized
1101 if (AclMain::aclCheckCore('patients', 'reminder') && $GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_prw']) :
1102 // patient reminders collapse widget
1103 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('reminder'));
1104 $id = "patient_reminders_ps_expand";
1105 $viewArgs = [
1106 'title' => xl('Patient Reminders'),
1107 'id' => $id,
1108 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1109 'btnLabel' => 'Edit',
1110 'btnLink' => '../reminder/patient_reminders.php?mode=simple&patient_id=' . attr_url($pid),
1111 'linkMethod' => 'html',
1112 'bodyClass' => 'notab collapse show',
1113 'auth' => AclMain::aclCheckCore('patients', 'reminder', '', 'write'),
1114 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1115 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1117 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1118 endif; //end if prw is activated
1120 if (AclMain::aclCheckCore('patients', 'disclosure')) :
1121 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('disclosure'));
1122 // disclosures expand collapse widget
1123 $id = "disclosures_ps_expand";
1124 $viewArgs = [
1125 'title' => xl('Disclosures'),
1126 'id' => $id,
1127 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1128 'btnLabel' => 'Edit',
1129 'btnLink' => 'disclosure_full.php',
1130 'linkMethod' => 'html',
1131 'bodyClass' => 'notab collapse show',
1132 'auth' => AclMain::aclCheckCore('patients', 'disclosure', '', 'write'),
1133 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1134 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1136 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1137 endif; // end if disclosures authorized
1139 if ($GLOBALS['amendments'] && AclMain::aclCheckCore('patients', 'amendment')) :
1140 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('amendment'));
1141 // Amendments widget
1142 $sql = "SELECT * FROM amendments WHERE pid = ? ORDER BY amendment_date DESC";
1143 $result = sqlStatement($sql, [$pid]);
1144 $amendments = [];
1145 while ($row = sqlFetchArray($result)) {
1146 $amendments[] = $row;
1149 $id = "amendments_ps_expand";
1150 $viewArgs = [
1151 'title' => xl('Amendments'),
1152 'id' => $id,
1153 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1154 'btnLabel' => 'Edit',
1155 'btnLink' => $GLOBALS['webroot'] . "/interface/patient_file/summary/list_amendments.php?id=" . attr_url($pid),
1156 'btnCLass' => 'rx_modal',
1157 'linkMethod' => 'html',
1158 'bodyClass' => 'notab collapse show',
1159 'auth' => AclMain::aclCheckCore('patients', 'amendment', '', 'write'),
1160 'amendments' => $amendments,
1161 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1162 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1164 echo $twig->getTwig()->render('patient/card/amendments.html.twig', $viewArgs);
1165 endif; // end amendments authorized
1167 if (AclMain::aclCheckCore('patients', 'lab')) :
1168 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('lab'));
1169 // labdata expand collapse widget
1170 // check to see if any labdata exist
1171 $spruch = "SELECT procedure_report.date_collected AS date
1172 FROM procedure_report
1173 JOIN procedure_order ON procedure_report.procedure_order_id = procedure_order.procedure_order_id
1174 WHERE procedure_order.patient_id = ?
1175 ORDER BY procedure_report.date_collected DESC";
1176 $existLabdata = sqlQuery($spruch, array($pid));
1177 $widgetAuth = ($existLabdata) ? true : false;
1179 $id = "labdata_ps_expand";
1180 $viewArgs = [
1181 'title' => xl('Labs'),
1182 'id' => $id,
1183 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1184 'btnLabel' => 'Trend',
1185 'btnLink' => "../summary/labdata.php",
1186 'linkMethod' => 'html',
1187 'bodyClass' => 'collapse show',
1188 'auth' => $widgetAuth,
1189 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1190 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1192 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1193 endif; // end labs authorized
1195 if ($vitals_is_registered && AclMain::aclCheckCore('patients', 'med')) :
1196 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('vital_sign'));
1197 // vitals expand collapse widget
1198 // check to see if any vitals exist
1199 $existVitals = sqlQuery("SELECT * FROM form_vitals WHERE pid=?", array($pid));
1200 $widgetAuth = ($existVitals) ? true : false;
1202 $id = "vitals_ps_expand";
1203 $viewArgs = [
1204 'title' => xl('Vitals'),
1205 'id' => $id,
1206 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1207 'btnLabel' => 'Trend',
1208 'btnLink' => "../encounter/trend_form.php?formname=vitals",
1209 'linkMethod' => 'html',
1210 'bodyClass' => 'collapse show',
1211 'auth' => $widgetAuth,
1212 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1213 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1215 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1216 endif; // end vitals
1218 // if anyone wants to render anything after the patient demographic list
1219 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(RenderEvent::EVENT_SECTION_LIST_RENDER_AFTER, new RenderEvent($pid), 10);
1221 // This generates a section similar to Vitals for each LBF form that
1222 // supports charting. The form ID is used as the "widget label".
1223 $gfres = sqlStatement("SELECT grp_form_id AS option_id, grp_title AS title, grp_aco_spec
1224 FROM layout_group_properties
1225 WHERE grp_form_id LIKE 'LBF%'
1226 AND grp_group_id = ''
1227 AND grp_repeats > 0
1228 AND grp_activity = 1
1229 ORDER BY grp_seq, grp_title");
1231 while ($gfrow = sqlFetchArray($gfres)) :
1232 // $jobj = json_decode($gfrow['notes'], true);
1233 $LBF_ACO = empty($gfrow['grp_aco_spec']) ? false : explode('|', $gfrow['grp_aco_spec']);
1234 if ($LBF_ACO && !AclMain::aclCheckCore($LBF_ACO[0], $LBF_ACO[1])) {
1235 continue;
1238 // vitals expand collapse widget
1239 $widgetAuth = false;
1240 if (!$LBF_ACO || AclMain::aclCheckCore($LBF_ACO[0], $LBF_ACO[1], '', 'write')) {
1241 // check to see if any instances exist for this patient
1242 $existVitals = sqlQuery("SELECT * FROM forms WHERE pid = ? AND formdir = ? AND deleted = 0", [$pid, $vitals_form_id]);
1243 $widgetAuth = $existVitals;
1246 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent($gfrow['title']));
1247 $viewArgs = [
1248 'title' => xl($gfrow['title']),
1249 'id' => $vitals_form_id,
1250 'initiallyCollapsed' => getUserSetting($vitals_form_id) == true ? true : false,
1251 'btnLabel' => 'Trend',
1252 'btnLink' => "../encounter/trend_form.php?formname=vitals",
1253 'linkMethod' => 'html',
1254 'bodyClass' => 'notab collapse show',
1255 'auth' => $widgetAuth,
1256 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1257 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1259 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1260 endwhile; // end while
1262 </div> <!-- end left column div -->
1263 <div class="col-md-4">
1264 <!-- start right column div -->
1265 <?php
1267 if ($GLOBALS['erx_enable']) :
1268 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('demographics'));
1269 echo $twig->getTwig()->render('patient/partials/erx.html.twig', [
1270 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1271 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1273 endif;
1275 if ($GLOBALS['portal_onsite_two_enable']) :
1276 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('portal'));
1278 echo $twig->getTwig()->render('patient/partials/portal.html.twig', [
1279 'portalAuthorized' => portalAuthorized($pid),
1280 'portalLoginHref' => $portal_login_href,
1281 'title' => xl('Patient Portal'),
1282 'id' => 'patient_portal',
1283 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1284 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1285 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1287 endif;
1289 // If there is an ID Card or any Photos show the widget
1290 $photos = pic_array($pid, $GLOBALS['patient_photo_category_name']);
1291 if ($photos or $idcard_doc_id) {
1292 $id = "photos_ps_expand";
1293 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('patient_photo'));
1294 $viewArgs = [
1295 'title' => xl("ID Card / Photos"),
1296 'id' => $id,
1297 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1298 'btnLabel' => 'Edit',
1299 'linkMethod' => "javascript",
1300 'bodyClass' => 'collapse show',
1301 'auth' => false,
1302 'patientIDCategoryID' => $GLOBALS['patient_id_category_name'],
1303 'patientPhotoCategoryName' => $GLOBALS['patient_photo_category_name'],
1304 'photos' => $photos,
1305 'idCardDocID' => $idcard_doc_id,
1306 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1307 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1309 echo $twig->getTwig()->render('patient/card/photo.html.twig', $viewArgs);
1312 // Advance Directives
1313 if ($GLOBALS['advance_directives_warning']) {
1314 // advance directives expand collapse widget
1316 $counterFlag = false; //flag to record whether any categories contain ad records
1317 $query = "SELECT id FROM categories WHERE name='Advance Directive'";
1318 $myrow2 = sqlQuery($query);
1319 $advDirArr = [];
1320 if ($myrow2) {
1321 $parentId = $myrow2['id'];
1322 $query = "SELECT id, name FROM categories WHERE parent=?";
1323 $resNew1 = sqlStatement($query, array($parentId));
1324 while ($myrows3 = sqlFetchArray($resNew1)) {
1325 $categoryId = $myrows3['id'];
1326 $nameDoc = $myrows3['name'];
1327 $query = "SELECT documents.date, documents.id
1328 FROM documents
1329 INNER JOIN categories_to_documents ON categories_to_documents.document_id=documents.id
1330 WHERE categories_to_documents.category_id=?
1331 AND documents.foreign_id=?
1332 AND documents.deleted = 0
1333 ORDER BY documents.date DESC";
1334 $resNew2 = sqlStatement($query, array($categoryId, $pid));
1335 $limitCounter = 0; // limit to one entry per category
1336 while (($myrows4 = sqlFetchArray($resNew2)) && ($limitCounter == 0)) {
1337 $dateTimeDoc = $myrows4['date'];
1338 // remove time from datetime stamp
1339 $tempParse = explode(" ", $dateTimeDoc);
1340 $dateDoc = $tempParse[0];
1341 $idDoc = $myrows4['id'];
1342 $tmp = [
1343 'pid' => $pid,
1344 'docID' => $idDoc,
1345 'docName' => $nameDoc,
1346 'docDate' => $dateDoc,
1348 $advDirArr[] = $tmp;
1349 $limitCounter = $limitCounter + 1;
1350 $counterFlag = true;
1354 $id = "adv_directives_ps_expand";
1356 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('advance_directive'));
1357 $viewArgs = [
1358 'title' => xl("Advance Directives"),
1359 'id' => $id,
1360 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1361 'btnLabel' => 'Edit',
1362 'linkMethod' => "javascript",
1363 'btnLink' => "return advdirconfigure();",
1364 'bodyClass' => 'collapse show',
1365 'auth' => true,
1366 'advDirArr' => $advDirArr,
1367 'counterFlag' => $counterFlag,
1368 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1369 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1371 echo $twig->getTwig()->render('patient/card/adv_dir.html.twig', $viewArgs);
1373 } // close advanced dir block
1375 // Show Clinical Reminders for any user that has rules that are permitted.
1376 $clin_rem_check = resolve_rules_sql('', '0', true, '', $_SESSION['authUser']);
1377 $cdr = $GLOBALS['enable_cdr'];
1378 $cdr_crw = $GLOBALS['enable_cdr_crw'];
1379 if (!empty($clin_rem_check) && $cdr && $cdr_crw && AclMain::aclCheckCore('patients', 'alert')) {
1380 // clinical summary expand collapse widget
1381 $id = "clinical_reminders_ps_expand";
1382 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('clinical_reminders'));
1383 $viewArgs = [
1384 'title' => xl("Clinical Reminders"),
1385 'id' => $id,
1386 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1387 'btnLabel' => "Edit",
1388 'btnLink' => "../reminder/clinical_reminders.php?patient_id=" . attr_url($pid),
1389 'linkMethod' => "html",
1390 'auth' => AclMain::aclCheckCore('patients', 'alert', '', 'write'),
1391 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1392 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1394 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1395 } // end if crw
1397 $displayAppts = false;
1398 $displayRecurrAppts = false;
1399 $displayPastAppts = false;
1401 // Show current and upcoming appointments.
1402 // Recurring appointment support and Appointment Display Sets
1403 // added to Appointments by Ian Jardine ( epsdky ).
1404 if (isset($pid) && !$GLOBALS['disable_calendar'] && AclMain::aclCheckCore('patients', 'appt')) {
1405 $displayAppts = true;
1406 $current_date2 = date('Y-m-d');
1407 $events = array();
1408 $apptNum = (int) $GLOBALS['number_of_appts_to_show'];
1409 $apptNum2 = ($apptNum != 0) ? abs($apptNum) : 10;
1411 $mode1 = !$GLOBALS['appt_display_sets_option'];
1412 $colorSet1 = $GLOBALS['appt_display_sets_color_1'];
1413 $colorSet2 = $GLOBALS['appt_display_sets_color_2'];
1414 $colorSet3 = $GLOBALS['appt_display_sets_color_3'];
1415 $colorSet4 = $GLOBALS['appt_display_sets_color_4'];
1416 $extraAppts = ($mode1) ? 1 : 6;
1417 $extraApptDate = '';
1419 $past_appts = [];
1420 $recallArr = [];
1422 $events = fetchNextXAppts($current_date2, $pid, $apptNum2 + $extraAppts, true);
1424 if ($events) {
1425 $selectNum = 0;
1426 $apptNumber = count($events);
1428 if ($apptNumber <= $apptNum2) {
1429 $extraApptDate = '';
1431 } elseif ($mode1 && $apptNumber == $apptNum2 + 1) {
1432 $extraApptDate = $events[$apptNumber - 1]['pc_eventDate'];
1433 array_pop($events);
1434 --$apptNumber;
1435 $selectNum = 1;
1437 } elseif ($apptNumber == $apptNum2 + 6) {
1438 $extraApptDate = $events[$apptNumber - 1]['pc_eventDate'];
1439 array_pop($events);
1440 --$apptNumber;
1441 $selectNum = 2;
1443 } else { // mode 2 - $apptNum2 < $apptNumber < $apptNum2 + 6
1444 $extraApptDate = '';
1445 $selectNum = 2;
1449 $limitApptIndx = $apptNum2 - 1;
1450 $limitApptDate = $events[$limitApptIndx]['pc_eventDate'] ?? '';
1452 switch ($selectNum) {
1453 case 2:
1454 $lastApptIndx = $apptNumber - 1;
1455 $thisNumber = $lastApptIndx - $limitApptIndx;
1456 for ($i = 1; $i <= $thisNumber; ++$i) {
1457 if ($events[$limitApptIndx + $i]['pc_eventDate'] != $limitApptDate) {
1458 $extraApptDate = $events[$limitApptIndx + $i]['pc_eventDate'];
1459 $events = array_slice($events, 0, $limitApptIndx + $i);
1460 break;
1463 // Break in the loop to improve performance
1464 case 1:
1465 $firstApptIndx = 0;
1466 for ($i = 1; $i <= $limitApptIndx; ++$i) {
1467 if ($events[$limitApptIndx - $i]['pc_eventDate'] != $limitApptDate) {
1468 $firstApptIndx = $apptNum2 - $i;
1469 break;
1472 // Break in the loop to improve performance
1475 if ($extraApptDate) {
1476 if ($extraApptDate != $limitApptDate) {
1477 $apptStyle2 = " style='background-color:" . attr($colorSet3) . ";'";
1478 } else {
1479 $apptStyle2 = " style='background-color:" . attr($colorSet4) . ";'";
1484 $count = 0;
1485 $toggleSet = true;
1486 $priorDate = "";
1487 $therapyGroupCategories = array();
1488 $query = sqlStatement("SELECT pc_catid FROM openemr_postcalendar_categories WHERE pc_cattype = 3 AND pc_active = 1");
1489 while ($result = sqlFetchArray($query)) {
1490 $therapyGroupCategories[] = $result['pc_catid'];
1493 // Build the UI Loop
1494 $appts = [];
1495 foreach ($events as $row) {
1496 $count++;
1497 $dayname = date("D", strtotime($row['pc_eventDate']));
1498 $displayMeridiem = ($GLOBALS['time_display_format'] == 0) ? "" : "am";
1499 $disphour = substr($row['pc_startTime'], 0, 2) + 0;
1500 $dispmin = substr($row['pc_startTime'], 3, 2);
1501 if ($disphour >= 12 && $GLOBALS['time_display_format'] == 1) {
1502 $displayMeridiem = "pm";
1503 if ($disphour > 12) {
1504 $disphour -= 12;
1508 // Note the translaution occurs here instead of in teh Twig file for some specific concatenation needs
1509 $etitle = xl('(Click to edit)');
1510 if ($row['pc_hometext'] != "") {
1511 $etitle = xl('Comments') . ": " . ($row['pc_hometext']) . "\r\n" . $etitle;
1514 if ($extraApptDate && $count > $firstApptIndx) {
1515 $apptStyle = $apptStyle2;
1516 } else {
1517 if ($row['pc_eventDate'] != $priorDate) {
1518 $priorDate = $row['pc_eventDate'];
1519 $toggleSet = !$toggleSet;
1522 $bgColor = ($toggleSet) ? $colorSet2 : $colorSet1;
1525 $row['pc_eventTime'] = sprintf("%02d", $disphour) . ":{$dispmin}";
1526 $row['pc_status'] = generate_display_field(array('data_type' => '1', 'list_id' => 'apptstat'), $row['pc_apptstatus']);
1528 if (in_array($row['pc_catid'], $therapyGroupCategories)) {
1529 $row['groupName'] = getGroup($row['pc_gid'])['group_name'];
1532 $row['uname'] = text($row['ufname'] . " " . $row['ulname']);
1533 $row['bgColor'] = $bgColor;
1534 $row['dayName'] = $dayname;
1535 $row['displayMeridiem'] = $displayMeridiem;
1536 $row['jsEvent'] = attr_js(preg_replace("/-/", "", $row['pc_eventDate'])) . ', ' . attr_js($row['pc_eid']);
1537 $appts[] = $row;
1540 if ($resNotNull) {
1541 // Show Recall if one exists
1542 $query = sqlStatement("SELECT * FROM medex_recalls WHERE r_pid = ?", [$pid]);
1543 $recallArr = [];
1544 while ($result2 = sqlFetchArray($query)) {
1545 //tabYourIt('recall', 'main/messages/messages.php?go=' + choice);
1546 //parent.left_nav.loadFrame('1', tabNAME, url);
1547 $recallArr[] = [
1548 'date' => $result2['r_eventDate'],
1549 'reason' => $result2['r_reason'],
1551 $count2++;
1553 $id = "recall_ps_expand";
1554 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('recall'));
1555 echo $twig->getTwig()->render('patient/card/recall.html.twig', [
1556 'title' => xl('Recall'),
1557 'id' => $id,
1558 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1559 'recalls' => $recallArr,
1560 'recallsAvailable' => ($count < 1 && empty($count2)) ? false : true,
1561 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1562 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1565 } // End of Appointments Widget.
1567 /* Widget that shows recurrences for appointments. */
1568 $recurr = [];
1569 if (isset($pid) && !$GLOBALS['disable_calendar'] && $GLOBALS['appt_recurrences_widget'] && AclMain::aclCheckCore('patients', 'appt')) {
1570 $displayRecurrAppts = true;
1571 $count = 0;
1572 $toggleSet = true;
1573 $priorDate = "";
1575 //Fetch patient's recurrences. Function returns array with recurrence appointments' category, recurrence pattern (interpreted), and end date.
1576 $recurrences = fetchRecurrences($pid);
1577 if (!empty($recurrences)) {
1578 foreach ($recurrences as $row) {
1579 if (!recurrence_is_current($row['pc_endDate'])) {
1580 continue;
1583 if (ends_in_a_week($row['pc_endDate'])) {
1584 $row['close_to_end'] = true;
1586 $recurr[] = $row;
1590 /* End of recurrence widget */
1592 // Show PAST appointments.
1593 // added by Terry Hill to allow reverse sorting of the appointments
1594 $direction = "ASC";
1595 if ($GLOBALS['num_past_appointments_to_show'] < 0) {
1596 $direction = "DESC";
1597 ($showpast = -1 * $GLOBALS['num_past_appointments_to_show']);
1598 } else {
1599 $showpast = $GLOBALS['num_past_appointments_to_show'];
1602 if (isset($pid) && !$GLOBALS['disable_calendar'] && $showpast > 0 && AclMain::aclCheckCore('patients', 'appt')) {
1603 $displayPastAppts = true;
1604 $query = "SELECT e.pc_eid, e.pc_aid, e.pc_title, e.pc_eventDate, e.pc_startTime, e.pc_hometext, u.fname, u.lname, u.mname, c.pc_catname, e.pc_apptstatus
1605 FROM openemr_postcalendar_events AS e,
1606 users AS u,
1607 openemr_postcalendar_categories AS c
1608 WHERE e.pc_pid = ?
1609 AND e.pc_eventDate < CURRENT_DATE
1610 AND u.id = e.pc_aid
1611 AND e.pc_catid = c.pc_catid
1612 ORDER BY e.pc_eventDate " . escape_sort_order($direction) . " , e.pc_startTime DESC LIMIT " . escape_limit($showpast);
1614 $pres = sqlStatement($query, array($pid));
1616 $count = 0;
1618 while ($row = sqlFetchArray($pres)) {
1619 $count++;
1620 $dayname = date("D", strtotime($row['pc_eventDate']));
1621 $displayMeridiem = "am";
1622 $disphour = substr($row['pc_startTime'], 0, 2) + 0;
1623 $dispmin = substr($row['pc_startTime'], 3, 2);
1624 if ($disphour >= 12) {
1625 $displayMeridiem = "pm";
1626 if ($disphour > 12 && $GLOBALS['time_display_format'] == 1) {
1627 $disphour -= 12;
1631 $petitle = xl('(Click to edit)');
1632 if ($row['pc_hometext'] != "") {
1633 $petitle = xl('Comments') . ": " . ($row['pc_hometext']) . "\r\n" . $petitle;
1636 $row['pc_status'] = generate_display_field(array('data_type' => '1', 'list_id' => 'apptstat'), $row['pc_apptstatus']);
1637 $row['dayName'] = $dayname;
1638 $row['pc_eventTime'] = sprintf("%02d", $disphour) . ":{$dispmin}";
1639 $row['uname'] = text($row['fname'] . " " . $row['lname']);
1640 $past_appts[] = $row;
1643 // END of past appointments
1645 // Display the Appt card
1646 $id = "appointments_ps_expand";
1647 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('appointment'));
1648 echo $twig->getTwig()->render('patient/card/appointments.html.twig', [
1649 'title' => xl("Appointments"),
1650 'id' => $id,
1651 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1652 'btnLabel' => "Add",
1653 'btnLink' => "return newEvt()",
1654 'linkMethod' => "javascript",
1655 'appts' => $appts,
1656 'recurrAppts' => $recurr,
1657 'pastAppts' => $past_appts,
1658 'displayAppts' => $displayAppts,
1659 'displayRecurrAppts' => $displayRecurrAppts,
1660 'displayPastAppts' => $displayPastAppts,
1661 'extraApptDate' => $extraApptDate,
1662 'therapyGroupCategories' => $therapyGroupCategories,
1663 'auth' => $resNotNull && (AclMain::aclCheckCore('patients', 'appt', '', 'write') || AclMain::aclCheckCore('patients', 'appt', '', 'addonly')),
1664 'resNotNull' => $resNotNull,
1665 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1666 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1669 echo "<div id=\"stats_div\"></div>";
1671 // TRACK ANYTHING
1672 // Determine if track_anything form is in use for this site.
1673 $tmp = sqlQuery("SELECT count(*) AS count FROM registry WHERE directory = 'track_anything' AND state = 1");
1674 $track_is_registered = $tmp['count'];
1675 if ($track_is_registered) {
1676 $spruch = "SELECT id FROM forms WHERE pid = ? AND formdir = ?";
1677 $existTracks = sqlQuery($spruch, array($pid, "track_anything"));
1678 $id = "track_anything_ps_expand";
1679 $dispatchResult = $ed->dispatch(CardRenderEvent::EVENT_HANDLE, new CardRenderEvent('track_anything'));
1680 echo $twig->getTwig()->render('patient/card/loader.html.twig', [
1681 'title' => xl("Tracks"),
1682 'id' => $id,
1683 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1684 'btnLink' => "../../forms/track_anything/create.php",
1685 'linkMethod' => "html",
1686 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1687 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1689 } // end track_anything
1691 if ($thisauth) :
1692 echo $twig->getTwig()->render('patient/partials/delete.html.twig', [
1693 'isAdmin' => AclMain::aclCheckCore('admin', 'super'),
1694 'allowPatientDelete' => $GLOBALS['allow_pat_delete'],
1695 'csrf' => CsrfUtils::collectCsrfToken(),
1696 'pid' => $pid
1698 endif;
1700 </div> <!-- end right column div -->
1701 </div> <!-- end div.main > row:first -->
1702 </div> <!-- end main content div -->
1703 </div><!-- end container div -->
1704 <?php $oemr_ui->oeBelowContainerDiv(); ?>
1705 <script>
1706 // Array of skip conditions for the checkSkipConditions() function.
1707 var skipArray = [
1708 <?php echo ($condition_str ?? ''); ?>
1710 checkSkipConditions();
1712 var isPost = <?php echo js_escape($showEligibility ?? false); ?>;
1713 var listId = '#' + <?php echo js_escape($list_id); ?>;
1714 $(function() {
1715 $(listId).addClass("active");
1716 if (isPost === true) {
1717 $("#eligibility").click();
1718 $("#eligibility").get(0).scrollIntoView();
1721 </script>
1722 </body>
1724 </html>