fix: Ensure proper rendering of top cards (#6762)
[openemr.git] / interface / patient_file / summary / demographics.php
blobf0e7a6fefe75b0d540206905c965c90dbd998c15
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.php");
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.php");
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\SectionEvent;
40 use OpenEMR\Events\Patient\Summary\Card\RenderModel;
41 use OpenEMR\Events\Patient\Summary\Card\CardInterface;
42 use OpenEMR\Events\PatientDemographics\ViewEvent;
43 use OpenEMR\Events\PatientDemographics\RenderEvent;
44 use OpenEMR\FHIR\SMART\SmartLaunchController;
45 use OpenEMR\Menu\PatientMenuRole;
46 use OpenEMR\OeUI\OemrUI;
47 use OpenEMR\Patient\Cards\PortalCard;
48 use OpenEMR\Reminder\BirthdayReminder;
49 use OpenEMR\Services\AllergyIntoleranceService;
50 use OpenEMR\Services\ConditionService;
51 use OpenEMR\Services\ImmunizationService;
52 use OpenEMR\Services\PatientIssuesService;
53 use OpenEMR\Services\PatientService;
54 use Symfony\Component\EventDispatcher\EventDispatcher;
56 $twig = new TwigContainer(null, $GLOBALS['kernel']);
58 // Set session for pid (via setpid). Also set session for encounter (if applicable)
59 if (isset($_GET['set_pid'])) {
60 require_once("$srcdir/pid.inc.php");
61 setpid($_GET['set_pid']);
62 $ptService = new PatientService();
63 $newPatient = $ptService->findByPid($pid);
64 $ptService->touchRecentPatientList($newPatient);
65 if (isset($_GET['set_encounterid']) && ((int)$_GET['set_encounterid'] > 0)) {
66 $encounter = (int)$_GET['set_encounterid'];
67 SessionUtil::setSession('encounter', $encounter);
71 // Note: it would eventually be a good idea to move this into
72 // it's own module that people can remove / add if they don't
73 // want smart support in their system.
74 $smartLaunchController = new SMARTLaunchController($GLOBALS["kernel"]->getEventDispatcher());
75 $smartLaunchController->registerContextEvents();
77 /**
78 * @var EventDispatcher
80 $ed = $GLOBALS['kernel']->getEventDispatcher();
82 $active_reminders = false;
83 $all_allergy_alerts = false;
84 if ($GLOBALS['enable_cdr']) {
85 //CDR Engine stuff
86 if ($GLOBALS['enable_allergy_check'] && $GLOBALS['enable_alert_log']) {
87 //Check for new allergies conflicts and throw popup if any exist(note need alert logging to support this)
88 $new_allergy_alerts = allergy_conflict($pid, 'new', $_SESSION['authUser']);
89 if (!empty($new_allergy_alerts)) {
90 $pod_warnings = '';
91 foreach ($new_allergy_alerts as $new_allergy_alert) {
92 $pod_warnings .= js_escape($new_allergy_alert) . ' + "\n"';
94 $allergyWarningMessage = '<script>alert(' . xlj('WARNING - FOLLOWING ACTIVE MEDICATIONS ARE ALLERGIES') . ' + "\n" + ' . $pod_warnings . ')</script>';
98 if ((empty($_SESSION['alert_notify_pid']) || ($_SESSION['alert_notify_pid'] != $pid)) && isset($_GET['set_pid']) && $GLOBALS['enable_cdr_crp']) {
99 // showing a new patient, so check for active reminders and allergy conflicts, which use in active reminder popup
100 $active_reminders = active_alert_summary($pid, "reminders-due", '', 'default', $_SESSION['authUser'], true);
101 if ($GLOBALS['enable_allergy_check']) {
102 $all_allergy_alerts = allergy_conflict($pid, 'all', $_SESSION['authUser'], true);
105 SessionUtil::setSession('alert_notify_pid', $pid);
106 // can not output html until after above setSession call
107 if (!empty($allergyWarningMessage)) {
108 echo $allergyWarningMessage;
111 //Check to see is only one insurance is allowed
112 if ($GLOBALS['insurance_only_one']) {
113 $insurance_array = array('primary');
114 } else {
115 $insurance_array = array('primary', 'secondary', 'tertiary');
118 function print_as_money($money)
120 preg_match("/(\d*)\.?(\d*)/", $money, $moneymatches);
121 $tmp = wordwrap(strrev($moneymatches[1]), 3, ",", 1);
122 $ccheck = strrev($tmp);
123 if ($ccheck[0] == ",") {
124 $tmp = substr($ccheck, 1, strlen($ccheck) - 1);
127 if ($moneymatches[2] != "") {
128 return "$ " . strrev($tmp) . "." . $moneymatches[2];
129 } else {
130 return "$ " . strrev($tmp);
134 // get an array from Photos category
135 function pic_array($pid, $picture_directory)
137 $pics = array();
138 $sql_query = "select documents.id from documents join categories_to_documents " .
139 "on documents.id = categories_to_documents.document_id " .
140 "join categories on categories.id = categories_to_documents.category_id " .
141 "where categories.name like ? and documents.foreign_id = ? and documents.deleted = 0";
142 if ($query = sqlStatement($sql_query, array($picture_directory, $pid))) {
143 while ($results = sqlFetchArray($query)) {
144 array_push($pics, $results['id']);
148 return ($pics);
151 // Get the document ID's in a specific catg.
152 // this is only used in one place, here for id photos
153 function get_document_by_catg($pid, $doc_catg, $limit = 1)
155 $results = null;
157 if ($pid and $doc_catg) {
158 $query = sqlStatement("SELECT d.id, d.date, d.url
159 FROM documents AS d, categories_to_documents AS cd, categories AS c
160 WHERE d.foreign_id = ?
161 AND cd.document_id = d.id
162 AND c.id = cd.category_id
163 AND c.name LIKE ?
164 ORDER BY d.date DESC LIMIT " . escape_limit($limit), array($pid, $doc_catg));
166 while ($result = sqlFetchArray($query)) {
167 $results[] = $result['id'];
169 return ($results ?? false);
172 function isPortalEnabled(): bool
174 if (
175 !$GLOBALS['portal_onsite_two_enable']
177 return false;
180 return true;
183 function isPortalSiteAddressValid(): bool
185 if (
186 // maybe can use filter_var() someday but the default value in GLOBALS
187 // fails with FILTER_VALIDATE_URL
188 !isset($GLOBALS['portal_onsite_two_address'])
190 return false;
193 return true;
196 function isPortalAllowed($pid): bool
198 $return = false;
200 $portalStatus = sqlQuery("SELECT allow_patient_portal FROM patient_data WHERE pid = ?", [$pid]);
201 if ($portalStatus['allow_patient_portal'] == 'YES') {
202 $return = true;
204 return $return;
207 function isApiAllowed($pid): bool
209 $return = false;
211 $apiStatus = sqlQuery("SELECT prevent_portal_apps FROM patient_data WHERE pid = ?", [$pid]);
212 if (strtoupper($apiStatus['prevent_portal_apps'] ?? '') != 'YES') {
213 $return = true;
215 return $return;
218 function areCredentialsCreated($pid): bool
220 $return = false;
221 $credentialsCreated = sqlQuery("SELECT date_created FROM `patient_access_onsite` WHERE `pid`=?", [$pid]);
222 if ($credentialsCreated['date_created'] ?? null) {
223 $return = true;
226 return $return;
229 function isContactEmail($pid): bool
231 $return = false;
233 $email = sqlQuery("SELECT email, email_direct FROM patient_data WHERE pid = ?", [$pid]);
234 if (!empty($email['email']) || !empty($email['email_direct'])) {
235 $return = true;
237 return $return;
240 function isEnforceSigninEmailPortal(): bool
242 if (
243 $GLOBALS['enforce_signin_email']
245 return true;
248 return false;
251 function deceasedDays($days_deceased)
253 $deceased_days = intval($days_deceased['days_deceased'] ?? '');
254 if ($deceased_days == 0) {
255 $num_of_days = xl("Today");
256 } elseif ($deceased_days == 1) {
257 $num_of_days = $deceased_days . " " . xl("day ago");
258 } elseif ($deceased_days > 1 && $deceased_days < 90) {
259 $num_of_days = $deceased_days . " " . xl("days ago");
260 } elseif ($deceased_days >= 90 && $deceased_days < 731) {
261 $num_of_days = "~" . round($deceased_days / 30) . " " . xl("months ago"); // function intdiv available only in php7
262 } elseif ($deceased_days >= 731) {
263 $num_of_days = xl("More than") . " " . round($deceased_days / 365) . " " . xl("years ago");
266 if (strlen($days_deceased['date_deceased'] ?? '') > 10 && $GLOBALS['date_display_format'] < 1) {
267 $deceased_date = substr($days_deceased['date_deceased'], 0, 10);
268 } else {
269 $deceased_date = oeFormatShortDate($days_deceased['date_deceased'] ?? '');
272 return xlt("Deceased") . " - " . text($deceased_date) . " (" . text($num_of_days) . ")";
275 $deceased = is_patient_deceased($pid);
278 // Display image in 'widget style'
279 function image_widget($doc_id, $doc_catg)
281 global $pid, $web_root;
282 $docobj = new Document($doc_id);
283 $image_file = $docobj->get_url_file();
284 $image_file_name = $docobj->get_name();
285 $image_width = $GLOBALS['generate_doc_thumb'] == 1 ? '' : 'width=100';
286 $extension = substr($image_file_name, strrpos($image_file_name, "."));
287 $viewable_types = array('.png', '.jpg', '.jpeg', '.png', '.bmp', '.PNG', '.JPG', '.JPEG', '.PNG', '.BMP');
288 if (in_array($extension, $viewable_types)) { // extension matches list
289 $to_url = "<td> <a href = '$web_root" .
290 "/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'" .
291 " onclick='top.restoreSession();' class='image_modal'>" .
292 " <img src = '$web_root" .
293 "/controller.php?document&retrieve&patient_id=" . attr_url($pid) . "&document_id=" . attr_url($doc_id) . "&as_file=false'" .
294 " $image_width alt='" . attr($doc_catg) . ":" . attr($image_file_name) . "'> </a> </td> <td class='align-middle'>" .
295 text($doc_catg) . '<br />&nbsp;' . text($image_file_name) . "</td>";
296 } else {
297 $to_url = "<td> <a href='" . $web_root . "/controller.php?document&retrieve" .
298 "&patient_id=" . attr_url($pid) . "&document_id=" . attr_url($doc_id) . "'" .
299 " onclick='top.restoreSession()' class='btn btn-primary btn-sm'>" .
300 "<span>" .
301 xlt("View") . "</a> &nbsp;" .
302 text("$doc_catg - $image_file_name") .
303 "</span> </td>";
306 echo "<table><tr>";
307 echo $to_url;
308 echo "</tr></table>";
311 // Determine if the Vitals form is in use for this site.
312 $tmp = sqlQuery("SELECT count(*) AS count FROM registry WHERE directory = 'vitals' AND state = 1");
313 $vitals_is_registered = $tmp['count'];
315 // Get patient/employer/insurance information.
317 $result = getPatientData($pid, "*, DATE_FORMAT(DOB,'%Y-%m-%d') as DOB_YMD");
318 $result2 = getEmployerData($pid);
319 $result3 = getInsuranceData(
320 $pid,
321 "primary",
322 "copay,
323 provider,
324 DATE_FORMAT(`date`,'%Y-%m-%d') as effdate,
325 DATE_FORMAT(`date_end`,'%Y-%m-%d') as effdate_end"
327 $insco_name = "";
328 if (!empty($result3['provider'])) { // Use provider in case there is an ins record w/ unassigned insco
329 $insco_name = getInsuranceProvider($result3['provider']);
332 $arrOeUiSettings = array(
333 'page_id' => 'core.mrd',
334 'heading_title' => xl('Medical Record Dashboard'),
335 'include_patient_name' => true,
336 'expandable' => true,
337 'expandable_files' => array(), //all file names need suffix _xpd
338 'action' => "", //conceal, reveal, search, reset, link or back
339 'action_title' => "",
340 'action_href' => "", //only for actions - reset, link or back
341 'show_help_icon' => true,
342 'help_file_name' => "medical_dashboard_help.php"
344 $oemr_ui = new OemrUI($arrOeUiSettings);
346 <!DOCTYPE html>
347 <html>
349 <head>
350 <?php
351 Header::setupHeader(['common','utility']);
352 require_once("$srcdir/options.js.php");
354 <script>
355 // Process click on diagnosis for referential cds popup.
356 function referentialCdsClick(codetype, codevalue) {
357 top.restoreSession();
358 // Force a new window instead of iframe to address cross site scripting potential
359 dlgopen('../education.php?type=' + encodeURIComponent(codetype) + '&code=' + encodeURIComponent(codevalue), '_blank', 1024, 750,true);
362 function oldEvt(apptdate, eventid) {
363 let title = <?php echo xlj('Appointments'); ?>;
364 dlgopen('../../main/calendar/add_edit_event.php?date=' + encodeURIComponent(apptdate) + '&eid=' + encodeURIComponent(eventid), '_blank', 800, 500, '', title);
367 function advdirconfigure() {
368 dlgopen('advancedirectives.php', '_blank', 400, 500);
371 function refreshme() {
372 top.restoreSession();
373 location.reload();
376 // Process click on Delete link.
377 function deleteme() { // @todo don't think this is used any longer!!
378 dlgopen('../deleter.php?patient=' + <?php echo js_url($pid); ?> + '&csrf_token_form=' + <?php echo js_url(CsrfUtils::collectCsrfToken()); ?>, '_blank', 500, 450, '', '', {
379 allowResize: false,
380 allowDrag: false,
381 dialogId: 'patdel',
382 type: 'iframe'
384 return false;
387 // Called by the deleteme.php window on a successful delete.
388 function imdeleted() {
389 top.clearPatient();
392 function newEvt() {
393 let title = <?php echo xlj('Appointments'); ?>;
394 let url = '../../main/calendar/add_edit_event.php?patientid=' + <?php echo js_url($pid); ?>;
395 dlgopen(url, '_blank', 800, 500, '', title);
396 return false;
399 function getWeno() {
400 top.restoreSession();
401 location.href = '../../weno/indexrx.php'
404 function toggleIndicator(target, div) {
405 // <i id="show_hide" class="fa fa-lg small fa-eye-slash" title="Click to Hide"></i>
406 $mode = $(target).find(".indicator").text();
407 if ($mode == <?php echo xlj('collapse'); ?>) {
408 $(target).find(".indicator").text(<?php echo xlj('expand'); ?>);
409 $("#" + div).hide();
410 $.post("../../../library/ajax/user_settings.php", {
411 target: div,
412 mode: 0,
413 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
415 } else {
416 $(target).find(".indicator").text(<?php echo xlj('collapse'); ?>);
417 $("#" + div).show();
418 $.post("../../../library/ajax/user_settings.php", {
419 target: div,
420 mode: 1,
421 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
426 // edit prescriptions dialog.
427 // called from stats.php.
429 function editScripts(url) {
430 var AddScript = function() {
431 var __this = $(this);
432 __this.find("#clearButton").css("display", "");
433 __this.find("#backButton").css("display", "");
434 __this.find("#addButton").css("display", "none");
436 var iam = top.frames.editScripts;
437 iam.location.href = '<?php echo $GLOBALS['webroot'] ?>/controller.php?prescription&edit&id=0&pid=' + <?php echo js_url($pid); ?>;
439 var ListScripts = function() {
440 var __this = $(this);
441 __this.find("#clearButton").css("display", "none");
442 __this.find("#backButton").css("display", "none");
443 __this.find("#addButton").css("display", "");
444 var iam = top.frames.editScripts
445 iam.location.href = '<?php echo $GLOBALS['webroot'] ?>/controller.php?prescription&list&id=' + <?php echo js_url($pid); ?>;
448 let title = <?php echo xlj('Prescriptions'); ?>;
449 let w = 960; // for weno width
451 dlgopen(url, 'editScripts', w, 400, '', '', {
452 buttons: [{
453 text: <?php echo xlj('Add'); ?>,
454 close: false,
455 id: 'addButton',
456 class: 'btn-primary btn-sm',
457 click: AddScript
460 text: <?php echo xlj('Clear'); ?>,
461 close: false,
462 id: 'clearButton',
463 style: 'display:none;',
464 class: 'btn-primary btn-sm',
465 click: AddScript
468 text: <?php echo xlj('Back'); ?>,
469 close: false,
470 id: 'backButton',
471 style: 'display:none;',
472 class: 'btn-primary btn-sm',
473 click: ListScripts
476 text: <?php echo xlj('Quit'); ?>,
477 close: true,
478 id: 'doneButton',
479 class: 'btn-secondary btn-sm'
482 onClosed: 'refreshme',
483 allowResize: true,
484 allowDrag: true,
485 dialogId: 'editscripts',
486 type: 'iframe'
488 return false;
492 * async function fetchHtml(...)
494 * @param {*} url
495 * @param {boolean} embedded
496 * @param {boolean} sessionRestore
497 * @returns {text}
499 async function fetchHtml(url, embedded = false, sessionRestore = false) {
500 if (sessionRestore === true) {
501 // restore cookie before fetch.
502 top.restoreSession();
504 let csrf = new FormData;
505 // a security given.
506 csrf.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
507 if (embedded === true) {
508 // special formatting in certain widgets.
509 csrf.append("embeddedScreen", true);
512 const response = await fetch(url, {
513 method: 'POST',
514 credentials: 'same-origin',
515 body: csrf
517 return await response.text();
521 * async function placeHtml(...) will await fetch of html then place in divId.
522 * This function will return a promise for use to init various items regarding
523 * inserted HTML if needed.
524 * If divId does not exist, then will skip.
525 * Example
527 * @param {*} url
528 * @param {string} divId id
529 * @param {boolean} embedded
530 * @param {boolean} sessionRestore
531 * @returns {object} promise
533 async function placeHtml(url, divId, embedded = false, sessionRestore = false) {
534 const contentDiv = document.getElementById(divId);
535 if (contentDiv) {
536 await fetchHtml(url, embedded, sessionRestore).then(fragment => {
537 contentDiv.innerHTML = fragment;
542 if (typeof load_location === 'undefined') {
543 function load_location(location) {
544 top.restoreSession();
545 document.location = location;
549 $(function() {
550 var msg_updation = '';
551 <?php
552 if ($GLOBALS['erx_enable']) {
553 $soap_status = sqlStatement("select soap_import_status,pid from patient_data where pid=? and soap_import_status in ('1','3')", array($pid));
554 while ($row_soapstatus = sqlFetchArray($soap_status)) { ?>
555 top.restoreSession();
556 $.ajax({
557 type: "POST",
558 url: "../../soap_functions/soap_patientfullmedication.php",
559 dataType: "html",
560 data: {
561 patient: <?php echo js_escape($row_soapstatus['pid']); ?>,
563 async: false,
564 success: function(thedata) {
565 //alert(thedata);
566 msg_updation += thedata;
568 error: function() {
569 alert('ajax error');
573 top.restoreSession();
574 $.ajax({
575 type: "POST",
576 url: "../../soap_functions/soap_allergy.php",
577 dataType: "html",
578 data: {
579 patient: <?php echo js_escape($row_soapstatus['pid']); ?>,
581 async: false,
582 success: function(thedata) {
583 //alert(thedata);
584 msg_updation += thedata;
586 error: function() {
587 alert('ajax error');
590 <?php
591 if ($GLOBALS['erx_import_status_message']) { ?>
592 if (msg_updation)
593 alert(msg_updation);
594 <?php
600 // load divs
601 placeHtml("stats.php", "stats_div", true);
602 placeHtml("pnotes_fragment.php", 'pnotes_ps_expand').then(() => {
603 // must be delegated event!
604 $(this).on("click", ".complete_btn", function() {
605 let btn = $(this);
606 let csrf = new FormData;
607 csrf.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
608 fetch("pnotes_fragment.php?docUpdateId=" + encodeURIComponent(btn.attr('data-id')), {
609 method: "POST",
610 credentials: 'same-origin',
611 body: csrf
613 .then(function() {
614 placeHtml("pnotes_fragment.php", 'pnotes_ps_expand');
618 placeHtml("disc_fragment.php", "disclosures_ps_expand");
619 placeHtml("labdata_fragment.php", "labdata_ps_expand");
620 placeHtml("track_anything_fragment.php", "track_anything_ps_expand");
621 <?php if ($vitals_is_registered && AclMain::aclCheckCore('patients', 'med')) { ?>
622 // Initialize the Vitals form if it is registered and user is authorized.
623 placeHtml("vitals_fragment.php", "vitals_ps_expand");
624 <?php } ?>
626 <?php if ($GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_crw']) { ?>
627 placeHtml("clinical_reminders_fragment.php", "clinical_reminders_ps_expand", true, true).then(() => {
628 // (note need to place javascript code here also to get the dynamic link to work)
629 $(".medium_modal").on('click', function(e) {
630 e.preventDefault();
631 e.stopPropagation();
632 dlgopen('', '', 800, 200, '', '', {
633 buttons: [{
634 text: <?php echo xlj('Close'); ?>,
635 close: true,
636 style: 'secondary btn-sm'
638 onClosed: 'refreshme',
639 allowResize: false,
640 allowDrag: true,
641 dialogId: 'demreminder',
642 type: 'iframe',
643 url: $(this).attr('href')
647 <?php } // end crw
650 <?php if ($GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_prw']) { ?>
651 placeHtml("patient_reminders_fragment.php", "patient_reminders_ps_expand", false, true);
652 <?php } // end prw
655 <?php
656 // Initialize for each applicable LBF form.
657 $gfres = sqlStatement("SELECT grp_form_id
658 FROM layout_group_properties
659 WHERE grp_form_id LIKE 'LBF%'
660 AND grp_group_id = ''
661 AND grp_repeats > 0
662 AND grp_activity = 1
663 ORDER BY grp_seq, grp_title");
664 while ($gfrow = sqlFetchArray($gfres)) { ?>
665 $(<?php echo js_escape("#" . $gfrow['grp_form_id'] . "_ps_expand"); ?>).load("lbf_fragment.php?formname=" + <?php echo js_url($gfrow['grp_form_id']); ?>, {
666 csrf_token_form: <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>
668 <?php } ?>
669 tabbify();
671 // modal for dialog boxes
672 $(".large_modal").on('click', function(e) {
673 e.preventDefault();
674 e.stopPropagation();
675 dlgopen('', '', 1000, 600, '', '', {
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 $(".rx_modal").on('click', function(e) {
690 e.preventDefault();
691 e.stopPropagation();
692 var title = <?php echo xlj('Amendments'); ?>;
693 dlgopen('', 'editAmendments', 800, 300, '', title, {
694 onClosed: 'refreshme',
695 allowResize: true,
696 allowDrag: true,
697 dialogId: '',
698 type: 'iframe',
699 url: $(this).attr('href')
703 // modal for image viewer
704 $(".image_modal").on('click', function(e) {
705 e.preventDefault();
706 e.stopPropagation();
707 dlgopen('', '', 400, 300, '', <?php echo xlj('Patient Images'); ?>, {
708 allowResize: true,
709 allowDrag: true,
710 dialogId: '',
711 type: 'iframe',
712 url: $(this).attr('href')
716 $(".deleter").on('click', function(e) {
717 e.preventDefault();
718 e.stopPropagation();
719 dlgopen('', '', 600, 360, '', '', {
720 buttons: [{
721 text: <?php echo xlj('Close'); ?>,
722 close: true,
723 style: 'secondary btn-sm'
725 //onClosed: 'imdeleted',
726 allowResize: false,
727 allowDrag: false,
728 dialogId: 'patdel',
729 type: 'iframe',
730 url: $(this).attr('href')
734 $(".iframe1").on('click', function(e) {
735 e.preventDefault();
736 e.stopPropagation();
737 dlgopen('', '', 350, 300, '', '', {
738 buttons: [{
739 text: <?php echo xlj('Close'); ?>,
740 close: true,
741 style: 'secondary btn-sm'
743 allowResize: true,
744 allowDrag: true,
745 dialogId: '',
746 type: 'iframe',
747 url: $(this).attr('href')
750 // for patient portal
751 $(".small_modal").on('click', function(e) {
752 e.preventDefault();
753 e.stopPropagation();
754 dlgopen('', '', 550, 550, '', '', {
755 buttons: [{
756 text: <?php echo xlj('Close'); ?>,
757 close: true,
758 style: 'secondary btn-sm'
760 allowResize: true,
761 allowDrag: true,
762 dialogId: '',
763 type: 'iframe',
764 url: $(this).attr('href')
768 function openReminderPopup() {
769 top.restoreSession()
770 dlgopen('', 'reminders', 500, 250, '', '', {
771 buttons: [{
772 text: <?php echo xlj('Close'); ?>,
773 close: true,
774 style: 'secondary btn-sm'
776 allowResize: true,
777 allowDrag: true,
778 dialogId: '',
779 type: 'iframe',
780 url: $("#reminder_popup_link").attr('href')
784 <?php if ($GLOBALS['patient_birthday_alert']) {
785 // To display the birthday alert:
786 // 1. The patient is not deceased
787 // 2. The birthday is today (or in the past depending on global selection)
788 // 3. The notification has not been turned off (or shown depending on global selection) for this year
789 $birthdayAlert = new BirthdayReminder($pid, $_SESSION['authUserID']);
790 if ($birthdayAlert->isDisplayBirthdayAlert()) {
792 // show the active reminder modal
793 dlgopen('', 'bdayreminder', 300, 170, '', false, {
794 allowResize: false,
795 allowDrag: true,
796 dialogId: '',
797 type: 'iframe',
798 url: $("#birthday_popup").attr('href')
801 <?php } elseif ($active_reminders || $all_allergy_alerts) { ?>
802 openReminderPopup();
803 <?php } ?>
804 <?php } elseif ($active_reminders || $all_allergy_alerts) { ?>
805 openReminderPopup();
806 <?php } ?>
808 // $(".card-title").on('click', "button", (e) => {
809 // console.debug("click");
810 // updateUserVisibilitySetting(e);
811 // });
815 * Change the preference to expand/collapse a given card.
817 * For the given e element, find the corresponding card body, determine if it is collapsed
818 * or shown, and then save the state to the user preferences via an async fetch call POST'ing
819 * the updated setting.
821 * @var e element The Button that was clicked to collapse/expand the card
823 async function updateUserVisibilitySetting(e) {
824 const targetID = e.target.getAttribute("data-target");
825 const target = document.querySelector(targetID);
826 const targetStr = targetID.substring(1);
828 let formData = new FormData();
829 formData.append("csrf_token_form", <?php echo js_escape(CsrfUtils::collectCsrfToken()); ?>);
830 formData.append("target", targetStr);
831 formData.append("mode", (target.classList.contains("show")) ? 0 : 1);
833 const response = await fetch("../../../library/ajax/user_settings.php", {
834 method: "POST",
835 credentials: 'same-origin',
836 body: formData,
839 const update = await response.text();
840 return update;
843 // Update the User's visibility setting when the card header is clicked
844 function cardTitleButtonClickListener() {
845 const buttons = document.querySelectorAll(".card-title a[data-toggle='collapse']");
846 buttons.forEach((b) => {
847 b.addEventListener("click", (e) => {
848 updateUserVisibilitySetting(e);
853 // JavaScript stuff to do when a new patient is set.
855 function setMyPatient() {
856 <?php
857 if (isset($_GET['set_pid'])) {
858 $date_of_death = is_patient_deceased($pid);
859 if (!empty($date_of_death)) {
860 $date_of_death = $date_of_death['date_deceased'];
863 parent.left_nav.setPatient(<?php echo js_escape($result['fname'] . " " . $result['lname']) .
864 "," . js_escape($pid) . "," . js_escape($result['pubpid']) . ",'',";
865 if (empty($date_of_death)) {
866 echo js_escape(" " . xl('DOB') . ": " . oeFormatShortDate($result['DOB_YMD']) . " " . xl('Age') . ": " . getPatientAgeDisplay($result['DOB_YMD']));
867 } else {
868 echo js_escape(" " . xl('DOB') . ": " . oeFormatShortDate($result['DOB_YMD']) . " " . xl('Age at death') . ": " . oeFormatAge($result['DOB_YMD'], $date_of_death));
869 } ?>);
870 var EncounterDateArray = new Array;
871 var CalendarCategoryArray = new Array;
872 var EncounterIdArray = new Array;
873 var Count = 0;
874 <?php
875 //Encounter details are stored to javacript as array.
876 $result4 = sqlStatement("SELECT fe.encounter,fe.date,openemr_postcalendar_categories.pc_catname FROM form_encounter AS fe " .
877 " left join openemr_postcalendar_categories on fe.pc_catid=openemr_postcalendar_categories.pc_catid WHERE fe.pid = ? order by fe.date desc", array($pid));
878 if (sqlNumRows($result4) > 0) {
879 while ($rowresult4 = sqlFetchArray($result4)) { ?>
880 EncounterIdArray[Count] = <?php echo js_escape($rowresult4['encounter']); ?>;
881 EncounterDateArray[Count] = <?php echo js_escape(oeFormatShortDate(date("Y-m-d", strtotime($rowresult4['date'])))); ?>;
882 CalendarCategoryArray[Count] = <?php echo js_escape(xl_appt_category($rowresult4['pc_catname'])); ?>;
883 Count++;
884 <?php
888 parent.left_nav.setPatientEncounter(EncounterIdArray, EncounterDateArray, CalendarCategoryArray);
889 <?php
890 } // end setting new pid
892 parent.left_nav.syncRadios();
893 <?php if ((isset($_GET['set_pid'])) && (isset($_GET['set_encounterid'])) && (intval($_GET['set_encounterid']) > 0)) {
894 $query_result = sqlQuery("SELECT `date` FROM `form_encounter` WHERE `encounter` = ?", array($encounter)); ?>
895 encurl = 'encounter/encounter_top.php?set_encounter=' + <?php echo js_url($encounter); ?> + '&pid=' + <?php echo js_url($pid); ?>;
896 parent.left_nav.setEncounter(<?php echo js_escape(oeFormatShortDate(date("Y-m-d", strtotime($query_result['date'])))); ?>, <?php echo js_escape($encounter); ?>, 'enc');
897 top.restoreSession();
898 parent.left_nav.loadFrame('enc2', 'enc', 'patient_file/' + encurl);
899 <?php } // end setting new encounter id (only if new pid is also set)
903 $(window).on('load', function() {
904 setMyPatient();
907 document.addEventListener("DOMContentLoaded", () => {
908 cardTitleButtonClickListener();
910 </script>
912 <style>
913 /* Bad practice to override here, will get moved to base style theme */
914 .card {
915 box-shadow: 1px 1px 1px hsl(0 0% 0% / .2);
916 border-radius: 0;
919 /* Short term fix. This ensures the problem list, allergies, medications, and immunization cards handle long lists without interuppting
920 the UI. This should be configurable and should go in a more appropriate place */
921 .pami-list {
922 max-height: 200px;
923 overflow-y: scroll;
926 <?php
927 if (!empty($GLOBALS['right_justify_labels_demographics']) && ($_SESSION['language_direction'] == 'ltr')) { ?>
928 div.tab td.label_custom, div.label_custom {
929 text-align: right !important;
932 div.tab td.data, div.data {
933 padding-left: 0.5em;
934 padding-right: 2em;
936 <?php
937 } ?>
939 <?php
940 // This is for layout font size override.
941 $grparr = array();
942 getLayoutProperties('DEM', $grparr, 'grp_size');
943 if (!empty($grparr['']['grp_size'])) {
944 $FONTSIZE = round($grparr['']['grp_size'] * 1.333333);
945 $FONTSIZE = round($FONTSIZE * 0.0625, 2);
948 /* Override font sizes in the theme. */
949 #DEM .groupname {
950 font-size: <?php echo attr($FONTSIZE); ?>rem;
953 #DEM .label {
954 font-size: <?php echo attr($FONTSIZE); ?>rem;
957 #DEM .data {
958 font-size: <?php echo attr($FONTSIZE); ?>rem;
961 #DEM .data td {
962 font-size: <?php echo attr($FONTSIZE); ?>rem;
965 <?php } ?> :root {
966 --white: #fff;
967 --bg: hsl(0 0% 90%);
970 body {
971 background: var(--bg) !important;
974 section {
975 background: var(--white);
976 margin-top: .25em;
977 padding: .25em;
980 .section-header-dynamic {
981 border-bottom: none;
983 </style>
984 <title><?php echo xlt("Dashboard{{patient file}}"); ?></title>
985 </head>
987 <body class="mt-1 patient-demographic bg-light">
989 <?php
990 // Create and fire the patient demographics view event
991 $viewEvent = new ViewEvent($pid);
992 $viewEvent = $GLOBALS["kernel"]->getEventDispatcher()->dispatch($viewEvent, ViewEvent::EVENT_HANDLE, 10);
993 $thisauth = AclMain::aclCheckCore('patients', 'demo');
995 if (!$thisauth || !$viewEvent->authorized()) {
996 echo $twig->getTwig()->render('core/unauthorized-partial.html.twig', ['pageTitle' => xl("Medical Dashboard")]);
997 exit();
1001 <div id="container_div" class="<?php echo $oemr_ui->oeContainer(); ?> mb-2">
1002 <a href='../reminder/active_reminder_popup.php' id='reminder_popup_link' style='display: none' onclick='top.restoreSession()'></a>
1003 <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>
1004 <?php
1006 if ($thisauth) {
1007 if ($result['squad'] && !AclMain::aclCheckCore('squads', $result['squad'])) {
1008 $thisauth = 0;
1012 if ($thisauth) :
1013 require_once("$include_root/patient_file/summary/dashboard_header.php");
1014 endif;
1016 $list_id = "dashboard"; // to indicate nav item is active, count and give correct id
1017 // Collect the patient menu then build it
1018 $menuPatient = new PatientMenuRole($twig);
1019 $menuPatient->displayHorizNavBarMenu();
1020 // Get the document ID of the patient ID card if access to it is wanted here.
1021 $idcard_doc_id = false;
1022 if ($GLOBALS['patient_id_category_name']) {
1023 $idcard_doc_id = get_document_by_catg($pid, $GLOBALS['patient_id_category_name'], 3);
1026 <div class="main mb-1">
1027 <!-- start main content div -->
1028 <div class="form-row">
1029 <?php
1030 $t = $twig->getTwig();
1032 $allergy = (AclMain::aclCheckIssue('allergy')) ? 1 : 0;
1033 $pl = (AclMain::aclCheckIssue('medical_problem')) ? 1 : 0;
1034 $meds = (AclMain::aclCheckIssue('medication')) ? 1 : 0;
1035 $rx = (!$GLOBALS['disable_prescriptions'] && AclMain::aclCheckCore('patients', 'rx')) ? 1 : 0;
1036 $cards = $allergy + $pl + $meds + $rx;
1037 $col = "p-1 ";
1039 $colInt = 12 / $cards;
1040 $col = "col-" . $colInt;
1043 * Helper function to return only issues with an outcome not equal to resolved
1045 * @param array $i An array of issues
1046 * @return array
1048 function filterActiveIssues(array $i): array
1050 return array_filter($i, function ($_i) {
1051 return $_i['outcome'] != 1;
1055 // ALLERGY CARD
1056 if (AclMain::aclCheckIssue('allergy')) {
1057 $allergyService = new AllergyIntoleranceService();
1058 $_rawAllergies = filterActiveIssues($allergyService->getAll(['lists.pid' => $pid])->getData());
1059 $_priority = [];
1060 $_standard = [];
1061 foreach ($_rawAllergies as $_) {
1062 if (in_array($_['severity_al'], ['severe', 'life_threatening_severity', 'fatal'])) {
1063 $_priority[] = $_;
1064 } else {
1065 $_standard[] = $_;
1068 $sql = "SELECT COUNT(*) as touched FROM lists_touch WHERE pid = ? AND type = 'allergy'";
1069 $result = sqlQuery($sql, [$pid]);
1071 $viewArgs = [
1072 'title' => xl('Allergies'),
1073 'card_container_class_list' => ['flex-fill', 'mx-1'],
1074 'id' => 'allergies_ps_expand',
1075 'forceAlwaysOpen' => false,
1076 'linkMethod' => "javascript",
1077 'list' => ['priority' => $_priority, 'standard' => $_standard],
1078 'listTouched' => ($result['touched'] > 0) ? true : false,
1079 'auth' => true,
1080 'btnLabel' => 'Edit',
1081 'btnLink' => "return load_location('{$GLOBALS['webroot']}/interface/patient_file/summary/stats_full.php?active=all&category=allergy')"
1083 echo "<div class=\"$col\">";
1084 echo $t->render('patient/card/allergies.html.twig', $viewArgs);
1085 echo "</div>";
1088 $patIssueService = new PatientIssuesService();
1090 // MEDICAL PROBLEMS CARD
1091 if (AclMain::aclCheckIssue('medical_problem')) {
1092 $_rawPL = $patIssueService->search(['lists.pid' => $pid, 'lists.type' => 'medical_problem'])->getData();
1093 $viewArgs = [
1094 'title' => xl('Medical Problems'),
1095 'card_container_class_list' => ['flex-fill', 'mx-1'],
1096 'id' => 'medical_problem_ps_expand',
1097 'forceAlwaysOpen' => false,
1098 'linkMethod' => "javascript",
1099 'list' => filterActiveIssues($_rawPL),
1100 'auth' => true,
1101 'btnLabel' => 'Edit',
1102 'btnLink' => "return load_location('{$GLOBALS['webroot']}/interface/patient_file/summary/stats_full.php?active=all&category=medical_problem')"
1104 echo "<div class=\"$col\">";
1105 echo $t->render('patient/card/medical_problems.html.twig', $viewArgs);
1106 echo "</div>";
1109 // MEDICATION CARD
1110 if (AclMain::aclCheckIssue('medication')) {
1111 $_rawMedList = $patIssueService->search(['lists.pid' => $pid, 'lists.type' => 'medication'])->getData();
1112 $viewArgs = [
1113 'title' => xl('Medications'),
1114 'card_container_class_list' => ['flex-fill', 'mx-1'],
1115 'id' => 'medications_ps_expand',
1116 'forceAlwaysOpen' => false,
1117 'linkMethod' => "javascript",
1118 'list' => filterActiveIssues($_rawMedList),
1119 'auth' => true,
1120 'btnLabel' => 'Edit',
1121 'btnLink' => "return load_location('{$GLOBALS['webroot']}/interface/patient_file/summary/stats_full.php?active=all&category=medication')"
1123 echo "<div class=\"$col\">";
1124 echo $t->render('patient/card/medication.html.twig', $viewArgs);
1125 echo "</div>";
1128 // Render the Prescriptions card if turned on
1129 if (!$GLOBALS['disable_prescriptions'] && AclMain::aclCheckCore('patients', 'rx')) :
1130 if ($GLOBALS['erx_enable'] && $display_current_medications_below == 1) {
1131 $sql = "SELECT * FROM prescriptions WHERE patient_id = ? AND active = '1'";
1132 $res = sqlStatement($sql, [$pid]);
1134 $rxArr = [];
1135 while ($row = sqlFetchArray($res)) {
1136 $row['unit'] = generate_display_field(array('data_type' => '1', 'list_id' => 'drug_units'), $row['unit']);
1137 $row['form'] = generate_display_field(array('data_type' => '1', 'list_id' => 'drug_form'), $row['form']);
1138 $row['route'] = generate_display_field(array('data_type' => '1', 'list_id' => 'drug_route'), $row['route']);
1139 $row['interval'] = generate_display_field(array('data_type' => '1', 'list_id' => 'drug_interval'), $row['interval']);
1140 $rxArr[] = $row;
1142 $id = "current_prescriptions_ps_expand";
1143 $viewArgs = [
1144 'title' => xl('Current Medications'),
1145 'id' => $id,
1146 'forceAlwaysOpen' => (getUserSetting($id) == 0) ? false : true,
1147 'auth' => false,
1148 'rxList' => $rxArr,
1151 echo $t->render('patient/card/erx.html.twig', $viewArgs);
1154 $id = "prescriptions_ps_expand";
1155 $viewArgs = [
1156 'title' => xl("Prescriptions"),
1157 'card_container_class_list' => ['flex-fill', 'mx-1'],
1158 'id' => $id,
1159 'forceAlwaysOpen' => (getUserSetting($id) == 0) ? false : true,
1160 'btnLabel' => "Edit",
1161 'auth' => AclMain::aclCheckCore('patients', 'rx', '', ['write', 'addonly']),
1164 if ($GLOBALS['erx_enable']) {
1165 $viewArgs['title'] = 'Prescription History';
1166 $viewArgs['btnLabel'] = 'Add';
1167 $viewArgs['btnLink'] = "{$GLOBALS['webroot']}/interface/eRx.php?page=compose";
1168 } elseif ($GLOBALS['weno_rx_enable']) {
1169 // weno plus button which opens their iframe
1170 $viewArgs['weno'] = true;
1171 $viewArgs['title'] = "WENO ComposeRx";
1172 $viewArgs['btnLabel'] = 'Add';
1173 $viewArgs['btnLink'] = "{$GLOBALS['webroot']}/interface/weno/indexrx.php";
1174 $viewArgs['btnClass'] = "iframe";
1175 $viewArgs['linkMethod'] = "javascript";
1176 $viewArgs['btnLink'] = "editScripts('{$GLOBALS['webroot']}/controller.php?prescription&list&id=" . attr_url($pid) . "')";
1177 } else {
1178 $viewArgs['btnLink'] = "editScripts('{$GLOBALS['webroot']}/controller.php?prescription&list&id=" . attr_url($pid) . "')";
1179 $viewArgs['linkMethod'] = "javascript";
1180 $viewArgs['btnClass'] = "iframe";
1183 $cwd = getcwd();
1184 chdir("../../../");
1185 $c = new Controller();
1186 // This is a hacky way to get a Smarty template from the controller and injecting it into
1187 // a Twig template. This reduces the amount of refactoring that is required but ideally the
1188 // Smarty template should be upgraded to Twig
1189 ob_start();
1190 echo $c->act(['prescription' => '', 'fragment' => '', 'patient_id' => $pid]);
1191 $viewArgs['content'] = ob_get_contents();
1192 ob_end_clean();
1194 echo "<div class=\"$col\">";
1195 echo $t->render('patient/card/rx.html.twig', $viewArgs);
1196 echo "</div>";
1197 endif;
1199 </div>
1200 </div>
1201 <div class="row">
1202 <div class="col-md-8">
1203 <?php
1204 if ($deceased > 0) :
1205 echo $twig->getTwig()->render('patient/partials/deceased.html.twig', [
1206 'deceasedDays' => deceasedDays($deceased),
1208 endif;
1210 $sectionRenderEvents = $ed->dispatch(new SectionEvent('primary'), SectionEvent::EVENT_HANDLE);
1211 $sectionCards = $sectionRenderEvents->getCards();
1213 foreach ($sectionCards as $card) {
1214 $_auth = $card->getAcl();
1215 if (!AclMain::aclCheckCore($_auth[0], $_auth[1])) {
1216 continue;
1219 $btnLabel = false;
1220 if ($card->canAdd()) {
1221 $btnLabel = 'Add';
1222 } elseif ($card->canEdit()) {
1223 $btnLabel = 'Edit';
1226 $viewArgs = [
1227 'title' => $card->getTitle(),
1228 'id' => $card->getIdentifier(),
1229 'initiallyCollapsed' => !$card->isInitiallyCollapsed(),
1230 'card_bg_color' => $card->getBackgroundColorClass(),
1231 'card_text_color' => $card->getTextColorClass(),
1232 'forceAlwaysOpen' => !$card->canCollapse(),
1233 'btnLabel' => $btnLabel,
1234 'btnLink' => 'test',
1237 echo $t->render($card->getTemplateFile(), array_merge($card->getTemplateVariables(), $viewArgs));
1240 if (!$GLOBALS['hide_billing_widget']) :
1241 $forceBillingExpandAlways = ($GLOBALS['force_billing_widget_open']) ? true : false;
1242 $patientbalance = get_patient_balance($pid, false);
1243 $insurancebalance = get_patient_balance($pid, true) - $patientbalance;
1244 $totalbalance = $patientbalance + $insurancebalance;
1245 $unallocated_amt = get_unallocated_patient_balance($pid);
1247 $id = "billing_ps_expand";
1248 $dispatchResult = $ed->dispatch(new CardRenderEvent('billing'), CardRenderEvent::EVENT_HANDLE);
1250 $viewArgs = [
1251 'title' => xl('Billing'),
1252 'id' => $id,
1253 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1254 'hideBtn' => true,
1255 'patientBalance' => $patientbalance,
1256 'insuranceBalance' => $insurancebalance,
1257 'totalBalance' => $totalbalance,
1258 'unallocated' => $unallocated_amt,
1259 'forceAlwaysOpen' => $forceBillingExpandAlways,
1260 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1261 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1264 if (!empty($result['billing_note'])) {
1265 $viewArgs['billingNote'] = $result['billing_note'];
1268 if (!empty($result3['provider'])) {
1269 $viewArgs['provider'] = true;
1270 $viewArgs['insName'] = $insco_name;
1271 $viewArgs['copay'] = $result3['copay'];
1272 $viewArgs['effDate'] = $result3['effdate'];
1273 $viewArgs['effDateEnd'] = $result3['effdate_end'];
1276 echo $twig->getTwig()->render('patient/card/billing.html.twig', $viewArgs);
1277 endif; // End the hide_billing_widget
1279 // if anyone wants to render anything before the patient demographic list
1280 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(new RenderEvent($pid), RenderEvent::EVENT_SECTION_LIST_RENDER_BEFORE, 10);
1282 if (AclMain::aclCheckCore('patients', 'demo')) :
1283 $dispatchResult = $ed->dispatch(new CardRenderEvent('demographic'), CardRenderEvent::EVENT_HANDLE);
1284 // Render the Demographics box
1285 $viewArgs = [
1286 'title' => xl("Demographics"),
1287 'id' => "demographics_ps_expand",
1288 'btnLabel' => "Edit",
1289 'btnLink' => "demographics_full.php",
1290 'linkMethod' => "html",
1291 'auth' => ACLMain::aclCheckCore('patients', 'demo', '', 'write'),
1292 'requireRestore' => (!isset($_SESSION['patient_portal_onsite_two'])) ? true : false,
1293 'initiallyCollapsed' => getUserSetting("demographics_ps_expand") == true ? true : false,
1294 'tabID' => "DEM",
1295 'result' => $result,
1296 'result2' => $result2,
1297 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1298 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1300 echo $twig->getTwig()->render('patient/card/tab_base.html.twig', $viewArgs);
1302 // Insurance
1303 $insArr = [];
1304 $insInBinder = '?';
1305 for ($y = 1; count($insurance_array) > $y; $y++) {
1306 $insInBinder .= ',?';
1308 $sql = "SELECT * FROM insurance_data WHERE pid = ? AND type IN(" . $insInBinder . ") ORDER BY type, date DESC";
1309 $params[] = $pid;
1310 $params = array_merge($params, $insurance_array);
1311 $res = sqlStatement($sql, $params);
1312 $prior_ins_type = '';
1314 while ($row = sqlFetchArray($res)) {
1315 if ($row['provider']) {
1316 // since the query is sorted by DATE DESC can use prior ins type to identify
1317 // unless insurance date_end is applicable
1318 $row['isOld'] = (!empty($row['date_end']) && $row['date_end'] <= date("Y-m-d")) ? true : false;
1319 if (!$row['isOld']) {
1320 $row['isOld'] = (strcmp($row['type'], $prior_ins_type) == 0) ? true : false;
1322 $icobj = new InsuranceCompany($row['provider']);
1323 $adobj = $icobj->get_address();
1324 $insco_name = trim($icobj->get_name());
1325 $row['insco'] = [
1326 'name' => trim($icobj->get_name()),
1327 'address' => [
1328 'line1' => $adobj->get_line1(),
1329 'line2' => $adobj->get_line2(),
1330 'city' => $adobj->get_city(),
1331 'state' => $adobj->get_state(),
1332 'postal' => $adobj->get_zip(),
1333 'country' => $adobj->get_country()
1336 $row['policy_type'] = (!empty($row['policy_type'])) ? $policy_types[$row['policy_type']] : false;
1337 $row['dispFromDate'] = $row['date'] ? true : false;
1338 $mname = ($row['subscriber_mname'] != "") ? $row['subscriber_mname'] : "";
1339 $row['subscriber_full_name'] = str_replace("%mname%", $mname, "{$row['subscriber_fname']} %mname% {$row['subscriber_lname']}");
1340 $row['until_date'] = $row['isOld'] ? $row['isOld'] : xlt('Present');
1341 $insArr[] = $row;
1342 $prior_ins_type = $row['type'];
1343 } else {
1344 $row['isOld'] = (strcmp($row['type'], $prior_ins_type) == 0) ? true : false;
1345 $row['dispFromDate'] = $row['date'] ? true : false;
1346 $row['insco'] = [
1347 'name' => 'Self-Pay',
1348 'address' => [
1349 'line1' => '',
1350 'line2' => '',
1351 'city' => '',
1352 'state' => '',
1353 'postal' => '',
1354 'country' => ''
1357 $row['policy_type'] = false;
1358 $mname = ''; //($row['subscriber_mname'] != "") ? $row['subscriber_mname'] : "";
1359 $row['subscriber_full_name'] = ' '; // str_replace("%mname%", $mname, "{$row['subscriber_fname']} %mname% {$row['subscriber_lname']}");
1360 $row['until_date'] = ($row['isOld']) ? $row['isOld'] : xlt("Present");
1361 $prior_ins_type = $row['type'];
1362 if ($row['type'] != 'primary') {
1363 continue;
1365 $insArr[] = $row;
1369 if ($GLOBALS["enable_oa"]) {
1370 if (($_POST['status_update'] ?? '') === 'true') {
1371 unset($_POST['status_update']);
1372 $showEligibility = true;
1373 $ok = EDI270::requestEligibleTransaction($pid);
1374 if ($ok === true) {
1375 ob_start();
1376 EDI270::showEligibilityInformation($pid, false);
1377 $output = ob_get_contents();
1378 ob_end_clean();
1379 } else {
1380 $output = $ok;
1382 } else {
1383 ob_start();
1384 EDI270::showEligibilityInformation($pid, true);
1385 $output = ob_get_contents();
1386 ob_end_clean();
1388 } else {
1389 ob_start();
1390 EDI270::showEligibilityInformation($pid, true);
1391 $output = ob_get_contents();
1392 ob_end_clean();
1395 $id = "insurance_ps_expand";
1396 $dispatchResult = $ed->dispatch(new CardRenderEvent('insurance'), CardRenderEvent::EVENT_HANDLE);
1397 $viewArgs = [
1398 'title' => xl("Insurance"),
1399 'id' => $id,
1400 'btnLabel' => "Edit",
1401 'btnLink' => "demographics_full.php",
1402 'linkMethod' => 'html',
1403 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1404 'ins' => $insArr,
1405 'eligibility' => $output,
1406 'enable_oa' => $GLOBALS['enable_oa'],
1407 'auth' => AclMain::aclCheckCore('patients', 'demo', '', 'write'),
1408 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1409 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1412 if (count($insArr) > 0) {
1413 echo $twig->getTwig()->render('patient/card/insurance.html.twig', $viewArgs);
1415 endif; // end if demographics authorized
1417 if (AclMain::aclCheckCore('patients', 'notes')) :
1418 $dispatchResult = $ed->dispatch(new CardRenderEvent('note'), CardRenderEvent::EVENT_HANDLE);
1419 // Notes expand collapse widget
1420 $id = "pnotes_ps_expand";
1421 $viewArgs = [
1422 'title' => xl("Messages"),
1423 'id' => $id,
1424 'btnLabel' => "Edit",
1425 'btnLink' => "pnotes_full.php?form_active=1",
1426 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1427 'linkMethod' => "html",
1428 'bodyClass' => "notab",
1429 'auth' => AclMain::aclCheckCore('patients', 'notes', '', 'write'),
1430 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1431 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1433 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1434 endif; // end if notes authorized
1436 if (AclMain::aclCheckCore('patients', 'reminder') && $GLOBALS['enable_cdr'] && $GLOBALS['enable_cdr_prw']) :
1437 // patient reminders collapse widget
1438 $dispatchResult = $ed->dispatch(new CardRenderEvent('reminder'), CardRenderEvent::EVENT_HANDLE);
1439 $id = "patient_reminders_ps_expand";
1440 $viewArgs = [
1441 'title' => xl('Patient Reminders'),
1442 'id' => $id,
1443 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1444 'btnLabel' => 'Edit',
1445 'btnLink' => '../reminder/patient_reminders.php?mode=simple&patient_id=' . attr_url($pid),
1446 'linkMethod' => 'html',
1447 'bodyClass' => 'notab collapse show',
1448 'auth' => AclMain::aclCheckCore('patients', 'reminder', '', 'write'),
1449 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1450 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1452 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1453 endif; //end if prw is activated
1455 if (AclMain::aclCheckCore('patients', 'disclosure')) :
1456 $authWriteDisclosure = AclMain::aclCheckCore('patients', 'disclosure', '', 'write');
1457 $authAddonlyDisclosure = AclMain::aclCheckCore('patients', 'disclosure', '', 'addonly');
1458 $dispatchResult = $ed->dispatch(new CardRenderEvent('disclosure'), CardRenderEvent::EVENT_HANDLE);
1459 // disclosures expand collapse widget
1460 $id = "disclosures_ps_expand";
1461 $viewArgs = [
1462 'title' => xl('Disclosures'),
1463 'id' => $id,
1464 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1465 'btnLabel' => 'Edit',
1466 'btnLink' => 'disclosure_full.php',
1467 'linkMethod' => 'html',
1468 'bodyClass' => 'notab collapse show',
1469 'auth' => ($authWriteDisclosure || $authAddonlyDisclosure),
1470 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1471 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1473 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1474 endif; // end if disclosures authorized
1476 if ($GLOBALS['amendments'] && AclMain::aclCheckCore('patients', 'amendment')) :
1477 $dispatchResult = $ed->dispatch(new CardRenderEvent('amendment'), CardRenderEvent::EVENT_HANDLE);
1478 // Amendments widget
1479 $sql = "SELECT * FROM amendments WHERE pid = ? ORDER BY amendment_date DESC";
1480 $result = sqlStatement($sql, [$pid]);
1481 $amendments = [];
1482 while ($row = sqlFetchArray($result)) {
1483 $amendments[] = $row;
1486 $id = "amendments_ps_expand";
1487 $viewArgs = [
1488 'title' => xl('Amendments'),
1489 'id' => $id,
1490 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1491 'btnLabel' => 'Edit',
1492 'btnLink' => $GLOBALS['webroot'] . "/interface/patient_file/summary/list_amendments.php?id=" . attr_url($pid),
1493 'btnCLass' => '',
1494 'linkMethod' => 'html',
1495 'bodyClass' => 'notab collapse show',
1496 'auth' => AclMain::aclCheckCore('patients', 'amendment', '', ['write', 'addonly']),
1497 'amendments' => $amendments,
1498 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1499 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1501 echo $twig->getTwig()->render('patient/card/amendments.html.twig', $viewArgs);
1502 endif; // end amendments authorized
1504 if (AclMain::aclCheckCore('patients', 'lab')) :
1505 $dispatchResult = $ed->dispatch(new CardRenderEvent('lab'), CardRenderEvent::EVENT_HANDLE);
1506 // labdata expand collapse widget
1507 // check to see if any labdata exist
1508 $spruch = "SELECT procedure_report.date_collected AS date
1509 FROM procedure_report
1510 JOIN procedure_order ON procedure_report.procedure_order_id = procedure_order.procedure_order_id
1511 WHERE procedure_order.patient_id = ?
1512 ORDER BY procedure_report.date_collected DESC";
1513 $existLabdata = sqlQuery($spruch, array($pid));
1514 $widgetAuth = ($existLabdata) ? true : false;
1516 $id = "labdata_ps_expand";
1517 $viewArgs = [
1518 'title' => xl('Labs'),
1519 'id' => $id,
1520 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1521 'btnLabel' => 'Trend',
1522 'btnLink' => "../summary/labdata.php",
1523 'linkMethod' => 'html',
1524 'bodyClass' => 'collapse show',
1525 'auth' => $widgetAuth,
1526 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1527 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1529 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1530 endif; // end labs authorized
1532 if ($vitals_is_registered && AclMain::aclCheckCore('patients', 'med')) :
1533 $dispatchResult = $ed->dispatch(new CardRenderEvent('vital_sign'), CardRenderEvent::EVENT_HANDLE);
1534 // vitals expand collapse widget
1535 // check to see if any vitals exist
1536 $existVitals = sqlQuery("SELECT * FROM form_vitals WHERE pid=?", array($pid));
1537 $widgetAuth = ($existVitals) ? true : false;
1539 $id = "vitals_ps_expand";
1540 $viewArgs = [
1541 'title' => xl('Vitals'),
1542 'id' => $id,
1543 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1544 'btnLabel' => 'Trend',
1545 'btnLink' => "../encounter/trend_form.php?formname=vitals&context=dashboard",
1546 'linkMethod' => 'html',
1547 'bodyClass' => 'collapse show',
1548 'auth' => $widgetAuth,
1549 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1550 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1552 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1553 endif; // end vitals
1555 // if anyone wants to render anything after the patient demographic list
1556 $GLOBALS["kernel"]->getEventDispatcher()->dispatch(new RenderEvent($pid), RenderEvent::EVENT_SECTION_LIST_RENDER_AFTER, 10);
1558 // This generates a section similar to Vitals for each LBF form that
1559 // supports charting. The form ID is used as the "widget label".
1560 $gfres = sqlStatement("SELECT grp_form_id AS option_id, grp_title AS title, grp_aco_spec
1561 FROM layout_group_properties
1562 WHERE grp_form_id LIKE 'LBF%'
1563 AND grp_group_id = ''
1564 AND grp_repeats > 0
1565 AND grp_activity = 1
1566 ORDER BY grp_seq, grp_title");
1568 while ($gfrow = sqlFetchArray($gfres)) :
1569 // $jobj = json_decode($gfrow['notes'], true);
1570 $LBF_ACO = empty($gfrow['grp_aco_spec']) ? false : explode('|', $gfrow['grp_aco_spec']);
1571 if ($LBF_ACO && !AclMain::aclCheckCore($LBF_ACO[0], $LBF_ACO[1])) {
1572 continue;
1575 // vitals expand collapse widget
1576 $widgetAuth = false;
1577 if (!$LBF_ACO || AclMain::aclCheckCore($LBF_ACO[0], $LBF_ACO[1], '', 'write')) {
1578 // check to see if any instances exist for this patient
1579 $existVitals = sqlQuery("SELECT * FROM forms WHERE pid = ? AND formdir = ? AND deleted = 0", [$pid, $vitals_form_id]);
1580 $widgetAuth = $existVitals;
1583 $dispatchResult = $ed->dispatch(new CardRenderEvent($gfrow['title']), CardRenderEvent::EVENT_HANDLE);
1584 $viewArgs = [
1585 'title' => xl($gfrow['title']),
1586 'id' => $vitals_form_id,
1587 'initiallyCollapsed' => getUserSetting($vitals_form_id) == true ? true : false,
1588 'btnLabel' => 'Trend',
1589 'btnLink' => "../encounter/trend_form.php?formname=vitals&context=dashboard",
1590 'linkMethod' => 'html',
1591 'bodyClass' => 'notab collapse show',
1592 'auth' => $widgetAuth,
1593 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1594 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1596 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1597 endwhile; // end while
1599 </div> <!-- end left column div -->
1600 <div class="col-md-4">
1601 <!-- start right column div -->
1602 <?php
1603 $_extAccess = [
1604 $GLOBALS['portal_onsite_two_enable'],
1605 $GLOBALS['rest_fhir_api'],
1606 $GLOBALS['rest_api'],
1607 $GLOBALS['rest_portal_api'],
1609 foreach ($_extAccess as $_) {
1610 if ($_) {
1611 $portalCard = new PortalCard($GLOBALS);
1612 break;
1616 $sectionRenderEvents = $ed->dispatch(new SectionEvent('secondary'), SectionEvent::EVENT_HANDLE);
1617 $sectionCards = $sectionRenderEvents->getCards();
1619 $t = $twig->getTwig();
1621 foreach ($sectionCards as $card) {
1622 $_auth = $card->getAcl();
1623 $auth = AclMain::aclCheckCore($_auth[0], $_auth[1]);
1624 if (!$auth) {
1625 continue;
1628 $btnLabel = false;
1629 if ($card->canAdd()) {
1630 $btnLabel = 'Add';
1631 } elseif ($card->canEdit()) {
1632 $btnLabel = 'Edit';
1635 $viewArgs = [
1636 'card' => $card,
1637 'title' => $card->getTitle(),
1638 'id' => $card->getIdentifier() . "_expand",
1639 'auth' => $auth,
1640 'linkMethod' => 'html',
1641 'initiallyCollapsed' => !$card->isInitiallyCollapsed(),
1642 'card_bg_color' => $card->getBackgroundColorClass(),
1643 'card_text_color' => $card->getTextColorClass(),
1644 'forceAlwaysOpen' => !$card->canCollapse(),
1645 'btnLabel' => $btnLabel,
1646 'btnLink' => "javascript:$('#patient_portal').collapse('toggle')",
1649 echo $t->render($card->getTemplateFile(), array_merge($card->getTemplateVariables(), $viewArgs));
1652 if ($GLOBALS['erx_enable']) :
1653 $dispatchResult = $ed->dispatch(new CardRenderEvent('demographics'), CardRenderEvent::EVENT_HANDLE);
1654 echo $twig->getTwig()->render('patient/partials/erx.html.twig', [
1655 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1656 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1658 endif;
1660 // If there is an ID Card or any Photos show the widget
1661 $photos = pic_array($pid, $GLOBALS['patient_photo_category_name']);
1662 if ($photos or $idcard_doc_id) {
1663 $id = "photos_ps_expand";
1664 $dispatchResult = $ed->dispatch(new CardRenderEvent('patient_photo'), CardRenderEvent::EVENT_HANDLE);
1665 $viewArgs = [
1666 'title' => xl("ID Card / Photos"),
1667 'id' => $id,
1668 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1669 'btnLabel' => 'Edit',
1670 'linkMethod' => "javascript",
1671 'bodyClass' => 'collapse show',
1672 'auth' => false,
1673 'patientIDCategoryID' => $GLOBALS['patient_id_category_name'],
1674 'patientPhotoCategoryName' => $GLOBALS['patient_photo_category_name'],
1675 'photos' => $photos,
1676 'idCardDocID' => $idcard_doc_id,
1677 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1678 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1680 echo $twig->getTwig()->render('patient/card/photo.html.twig', $viewArgs);
1683 // Advance Directives
1684 if ($GLOBALS['advance_directives_warning']) {
1685 // advance directives expand collapse widget
1687 $counterFlag = false; //flag to record whether any categories contain ad records
1688 $query = "SELECT id FROM categories WHERE name='Advance Directive'";
1689 $myrow2 = sqlQuery($query);
1690 $advDirArr = [];
1691 if ($myrow2) {
1692 $parentId = $myrow2['id'];
1693 $query = "SELECT id, name FROM categories WHERE parent=?";
1694 $resNew1 = sqlStatement($query, array($parentId));
1695 while ($myrows3 = sqlFetchArray($resNew1)) {
1696 $categoryId = $myrows3['id'];
1697 $nameDoc = $myrows3['name'];
1698 $query = "SELECT documents.date, documents.id
1699 FROM documents
1700 INNER JOIN categories_to_documents ON categories_to_documents.document_id=documents.id
1701 WHERE categories_to_documents.category_id=?
1702 AND documents.foreign_id=?
1703 AND documents.deleted = 0
1704 ORDER BY documents.date DESC";
1705 $resNew2 = sqlStatement($query, array($categoryId, $pid));
1706 $limitCounter = 0; // limit to one entry per category
1707 while (($myrows4 = sqlFetchArray($resNew2)) && ($limitCounter == 0)) {
1708 $dateTimeDoc = $myrows4['date'];
1709 // remove time from datetime stamp
1710 $tempParse = explode(" ", $dateTimeDoc);
1711 $dateDoc = $tempParse[0];
1712 $idDoc = $myrows4['id'];
1713 $tmp = [
1714 'pid' => $pid,
1715 'docID' => $idDoc,
1716 'docName' => $nameDoc,
1717 'docDate' => $dateDoc,
1719 $advDirArr[] = $tmp;
1720 $limitCounter = $limitCounter + 1;
1721 $counterFlag = true;
1725 $id = "adv_directives_ps_expand";
1727 $dispatchResult = $ed->dispatch(new CardRenderEvent('advance_directive'), CardRenderEvent::EVENT_HANDLE);
1728 $viewArgs = [
1729 'title' => xl("Advance Directives"),
1730 'id' => $id,
1731 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1732 'btnLabel' => 'Edit',
1733 'linkMethod' => "javascript",
1734 'btnLink' => "return advdirconfigure();",
1735 'bodyClass' => 'collapse show',
1736 'auth' => true,
1737 'advDirArr' => $advDirArr,
1738 'counterFlag' => $counterFlag,
1739 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1740 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1742 echo $twig->getTwig()->render('patient/card/adv_dir.html.twig', $viewArgs);
1744 } // close advanced dir block
1746 // Show Clinical Reminders for any user that has rules that are permitted.
1747 $clin_rem_check = resolve_rules_sql('', '0', true, '', $_SESSION['authUser']);
1748 $cdr = $GLOBALS['enable_cdr'];
1749 $cdr_crw = $GLOBALS['enable_cdr_crw'];
1750 if (!empty($clin_rem_check) && $cdr && $cdr_crw && AclMain::aclCheckCore('patients', 'alert')) {
1751 // clinical summary expand collapse widget
1752 $id = "clinical_reminders_ps_expand";
1753 $dispatchResult = $ed->dispatch(new CardRenderEvent('clinical_reminders'), CardRenderEvent::EVENT_HANDLE);
1754 $viewArgs = [
1755 'title' => xl("Clinical Reminders"),
1756 'id' => $id,
1757 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1758 'btnLabel' => "Edit",
1759 'btnLink' => "../reminder/clinical_reminders.php?patient_id=" . attr_url($pid),
1760 'linkMethod' => "html",
1761 'auth' => AclMain::aclCheckCore('patients', 'alert', '', 'write'),
1762 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1763 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1765 echo $twig->getTwig()->render('patient/card/loader.html.twig', $viewArgs);
1766 } // end if crw
1768 $displayAppts = false;
1769 $displayRecurrAppts = false;
1770 $displayPastAppts = false;
1772 // Show current and upcoming appointments.
1773 // Recurring appointment support and Appointment Display Sets
1774 // added to Appointments by Ian Jardine ( epsdky ).
1775 if (isset($pid) && !$GLOBALS['disable_calendar'] && AclMain::aclCheckCore('patients', 'appt')) {
1776 $displayAppts = true;
1777 $current_date2 = date('Y-m-d');
1778 $events = array();
1779 $apptNum = (int) $GLOBALS['number_of_appts_to_show'];
1780 $apptNum2 = ($apptNum != 0) ? abs($apptNum) : 10;
1782 $mode1 = !$GLOBALS['appt_display_sets_option'];
1783 $colorSet1 = $GLOBALS['appt_display_sets_color_1'];
1784 $colorSet2 = $GLOBALS['appt_display_sets_color_2'];
1785 $colorSet3 = $GLOBALS['appt_display_sets_color_3'];
1786 $colorSet4 = $GLOBALS['appt_display_sets_color_4'];
1787 $extraAppts = ($mode1) ? 1 : 6;
1788 $extraApptDate = '';
1790 $past_appts = [];
1791 $recallArr = [];
1793 $events = fetchNextXAppts($current_date2, $pid, $apptNum2 + $extraAppts, true);
1795 if ($events) {
1796 $selectNum = 0;
1797 $apptNumber = count($events);
1799 if ($apptNumber <= $apptNum2) {
1800 $extraApptDate = '';
1802 } elseif ($mode1 && $apptNumber == $apptNum2 + 1) {
1803 $extraApptDate = $events[$apptNumber - 1]['pc_eventDate'];
1804 array_pop($events);
1805 --$apptNumber;
1806 $selectNum = 1;
1808 } elseif ($apptNumber == $apptNum2 + 6) {
1809 $extraApptDate = $events[$apptNumber - 1]['pc_eventDate'];
1810 array_pop($events);
1811 --$apptNumber;
1812 $selectNum = 2;
1814 } else { // mode 2 - $apptNum2 < $apptNumber < $apptNum2 + 6
1815 $extraApptDate = '';
1816 $selectNum = 2;
1820 $limitApptIndx = $apptNum2 - 1;
1821 $limitApptDate = $events[$limitApptIndx]['pc_eventDate'] ?? '';
1823 switch ($selectNum) {
1824 case 2:
1825 $lastApptIndx = $apptNumber - 1;
1826 $thisNumber = $lastApptIndx - $limitApptIndx;
1827 for ($i = 1; $i <= $thisNumber; ++$i) {
1828 if ($events[$limitApptIndx + $i]['pc_eventDate'] != $limitApptDate) {
1829 $extraApptDate = $events[$limitApptIndx + $i]['pc_eventDate'];
1830 $events = array_slice($events, 0, $limitApptIndx + $i);
1831 break;
1834 // Break in the loop to improve performance
1835 case 1:
1836 $firstApptIndx = 0;
1837 for ($i = 1; $i <= $limitApptIndx; ++$i) {
1838 if ($events[$limitApptIndx - $i]['pc_eventDate'] != $limitApptDate) {
1839 $firstApptIndx = $apptNum2 - $i;
1840 break;
1843 // Break in the loop to improve performance
1846 if ($extraApptDate) {
1847 if ($extraApptDate != $limitApptDate) {
1848 $apptStyle2 = " style='background-color:" . attr($colorSet3) . ";'";
1849 } else {
1850 $apptStyle2 = " style='background-color:" . attr($colorSet4) . ";'";
1855 $count = 0;
1856 $toggleSet = true;
1857 $priorDate = "";
1858 $therapyGroupCategories = array();
1859 $query = sqlStatement("SELECT pc_catid FROM openemr_postcalendar_categories WHERE pc_cattype = 3 AND pc_active = 1");
1860 while ($result = sqlFetchArray($query)) {
1861 $therapyGroupCategories[] = $result['pc_catid'];
1864 // Build the UI Loop
1865 $appts = [];
1866 foreach ($events as $row) {
1867 $count++;
1868 $dayname = date("D", strtotime($row['pc_eventDate']));
1869 $displayMeridiem = ($GLOBALS['time_display_format'] == 0) ? "" : "am";
1870 $disphour = substr($row['pc_startTime'], 0, 2) + 0;
1871 $dispmin = substr($row['pc_startTime'], 3, 2);
1872 if ($disphour >= 12 && $GLOBALS['time_display_format'] == 1) {
1873 $displayMeridiem = "pm";
1874 if ($disphour > 12) {
1875 $disphour -= 12;
1879 // Note the translaution occurs here instead of in teh Twig file for some specific concatenation needs
1880 $etitle = xl('(Click to edit)');
1881 if ($row['pc_hometext'] != "") {
1882 $etitle = xl('Comments') . ": " . ($row['pc_hometext']) . "\r\n" . $etitle;
1885 $row['etitle'] = $etitle;
1887 if ($extraApptDate && $count > $firstApptIndx) {
1888 $apptStyle = $apptStyle2;
1889 } else {
1890 if ($row['pc_eventDate'] != $priorDate) {
1891 $priorDate = $row['pc_eventDate'];
1892 $toggleSet = !$toggleSet;
1895 $bgColor = ($toggleSet) ? $colorSet2 : $colorSet1;
1898 $row['pc_eventTime'] = sprintf("%02d", $disphour) . ":{$dispmin}";
1899 $row['pc_status'] = generate_display_field(array('data_type' => '1', 'list_id' => 'apptstat'), $row['pc_apptstatus']);
1900 if ($row['pc_status'] == 'None') {
1901 $row['pc_status'] = 'Scheduled';
1904 if (in_array($row['pc_catid'], $therapyGroupCategories)) {
1905 $row['groupName'] = getGroup($row['pc_gid'])['group_name'];
1908 $row['uname'] = text($row['ufname'] . " " . $row['ulname']);
1909 $row['bgColor'] = $bgColor;
1910 $row['dayName'] = $dayname;
1911 $row['displayMeridiem'] = $displayMeridiem;
1912 $row['jsEvent'] = attr_js(preg_replace("/-/", "", $row['pc_eventDate'])) . ', ' . attr_js($row['pc_eid']);
1913 $appts[] = $row;
1916 if ($resNotNull) {
1917 // Show Recall if one exists
1918 $query = sqlStatement("SELECT * FROM `medex_recalls` WHERE `r_pid` = ?", [(int)$pid]);
1919 $recallArr = [];
1920 while ($result2 = sqlFetchArray($query)) {
1921 //tabYourIt('recall', 'main/messages/messages.php?go=' + choice);
1922 //parent.left_nav.loadFrame('1', tabNAME, url);
1923 $recallArr[] = [
1924 'date' => $result2['r_eventDate'],
1925 'reason' => $result2['r_reason'],
1927 $count2++;
1929 $id = "recall_ps_expand";
1930 $dispatchResult = $ed->dispatch(new CardRenderEvent('recall'), CardRenderEvent::EVENT_HANDLE);
1931 echo $twig->getTwig()->render('patient/card/recall.html.twig', [
1932 'title' => xl('Recall'),
1933 'id' => $id,
1934 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
1935 'recalls' => $recallArr,
1936 'recallsAvailable' => ($count < 1 && empty($count2)) ? false : true,
1937 'prependedInjection' => $dispatchResult->getPrependedInjection(),
1938 'appendedInjection' => $dispatchResult->getAppendedInjection(),
1941 } // End of Appointments Widget.
1943 /* Widget that shows recurrences for appointments. */
1944 $recurr = [];
1945 if (isset($pid) && !$GLOBALS['disable_calendar'] && $GLOBALS['appt_recurrences_widget'] && AclMain::aclCheckCore('patients', 'appt')) {
1946 $displayRecurrAppts = true;
1947 $count = 0;
1948 $toggleSet = true;
1949 $priorDate = "";
1951 //Fetch patient's recurrences. Function returns array with recurrence appointments' category, recurrence pattern (interpreted), and end date.
1952 $recurrences = fetchRecurrences($pid);
1953 if (!empty($recurrences)) {
1954 foreach ($recurrences as $row) {
1955 if (!recurrence_is_current($row['pc_endDate'])) {
1956 continue;
1959 if (ends_in_a_week($row['pc_endDate'])) {
1960 $row['close_to_end'] = true;
1962 $recurr[] = $row;
1966 /* End of recurrence widget */
1968 // Show PAST appointments.
1969 // added by Terry Hill to allow reverse sorting of the appointments
1970 $direction = '1';
1971 if ($GLOBALS['num_past_appointments_to_show'] < 0) {
1972 $direction = '2';
1973 ($showpast = -1 * $GLOBALS['num_past_appointments_to_show']);
1974 } else {
1975 $showpast = $GLOBALS['num_past_appointments_to_show'];
1978 if (isset($pid) && !$GLOBALS['disable_calendar'] && $showpast > 0 && AclMain::aclCheckCore('patients', 'appt')) {
1979 $displayPastAppts = true;
1981 $pastAppts = fetchXPastAppts($pid, $showpast, $direction); // This line added by epsdky
1983 $count = 0;
1985 foreach ($pastAppts as $row) {
1986 $count++;
1987 $dayname = date("D", strtotime($row['pc_eventDate']));
1988 $displayMeridiem = ($GLOBALS['time_display_format'] == 0) ? "" : "am";
1989 $disphour = substr($row['pc_startTime'], 0, 2) + 0;
1990 $dispmin = substr($row['pc_startTime'], 3, 2);
1991 if ($disphour >= 12) {
1992 $displayMeridiem = "pm";
1993 if ($disphour > 12 && $GLOBALS['time_display_format'] == 1) {
1994 $disphour -= 12;
1998 $petitle = xl('(Click to edit)');
1999 if ($row['pc_hometext'] != "") {
2000 $petitle = xl('Comments') . ": " . ($row['pc_hometext']) . "\r\n" . $petitle;
2002 $row['etitle'] = $petitle;
2004 $row['pc_status'] = generate_display_field(array('data_type' => '1', 'list_id' => 'apptstat'), $row['pc_apptstatus']);
2006 $row['dayName'] = $dayname;
2007 $row['displayMeridiem'] = $displayMeridiem;
2008 $row['pc_eventTime'] = sprintf("%02d", $disphour) . ":{$dispmin}";
2009 $row['uname'] = text($row['ufname'] . " " . $row['ulname']);
2010 $row['jsEvent'] = attr_js(preg_replace("/-/", "", $row['pc_eventDate'])) . ', ' . attr_js($row['pc_eid']);
2011 $past_appts[] = $row;
2014 // END of past appointments
2016 // Display the Appt card
2017 $id = "appointments_ps_expand";
2018 $dispatchResult = $ed->dispatch(new CardRenderEvent('appointment'), CardRenderEvent::EVENT_HANDLE);
2019 echo $twig->getTwig()->render('patient/card/appointments.html.twig', [
2020 'title' => xl("Appointments"),
2021 'id' => $id,
2022 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
2023 'btnLabel' => "Add",
2024 'btnLink' => "return newEvt()",
2025 'linkMethod' => "javascript",
2026 'appts' => $appts,
2027 'recurrAppts' => $recurr,
2028 'pastAppts' => $past_appts,
2029 'displayAppts' => $displayAppts,
2030 'displayRecurrAppts' => $displayRecurrAppts,
2031 'displayPastAppts' => $displayPastAppts,
2032 'extraApptDate' => $extraApptDate,
2033 'therapyGroupCategories' => $therapyGroupCategories,
2034 'auth' => $resNotNull && (AclMain::aclCheckCore('patients', 'appt', '', 'write') || AclMain::aclCheckCore('patients', 'appt', '', 'addonly')),
2035 'resNotNull' => $resNotNull,
2036 'prependedInjection' => $dispatchResult->getPrependedInjection(),
2037 'appendedInjection' => $dispatchResult->getAppendedInjection(),
2040 echo "<div id=\"stats_div\"></div>";
2042 // TRACK ANYTHING
2043 // Determine if track_anything form is in use for this site.
2044 $tmp = sqlQuery("SELECT count(*) AS count FROM registry WHERE directory = 'track_anything' AND state = 1");
2045 $track_is_registered = $tmp['count'];
2046 if ($track_is_registered) {
2047 $spruch = "SELECT id FROM forms WHERE pid = ? AND formdir = ?";
2048 $existTracks = sqlQuery($spruch, array($pid, "track_anything"));
2049 $id = "track_anything_ps_expand";
2050 $dispatchResult = $ed->dispatch(new CardRenderEvent('track_anything'), CardRenderEvent::EVENT_HANDLE);
2051 echo $twig->getTwig()->render('patient/card/loader.html.twig', [
2052 'title' => xl("Tracks"),
2053 'id' => $id,
2054 'initiallyCollapsed' => (getUserSetting($id) == 0) ? false : true,
2055 'btnLink' => "../../forms/track_anything/create.php",
2056 'linkMethod' => "html",
2057 'prependedInjection' => $dispatchResult->getPrependedInjection(),
2058 'appendedInjection' => $dispatchResult->getAppendedInjection(),
2060 } // end track_anything
2062 if ($thisauth) :
2063 echo $twig->getTwig()->render('patient/partials/delete.html.twig', [
2064 'isAdmin' => AclMain::aclCheckCore('admin', 'super'),
2065 'allowPatientDelete' => $GLOBALS['allow_pat_delete'],
2066 'csrf' => CsrfUtils::collectCsrfToken(),
2067 'pid' => $pid
2069 endif;
2071 </div> <!-- end right column div -->
2072 </div> <!-- end div.main > row:first -->
2073 </div> <!-- end main content div -->
2074 </div><!-- end container div -->
2075 <?php $oemr_ui->oeBelowContainerDiv(); ?>
2076 <script>
2077 // Array of skip conditions for the checkSkipConditions() function.
2078 var skipArray = [
2079 <?php echo ($condition_str ?? ''); ?>
2081 checkSkipConditions();
2085 var isPost = <?php echo js_escape($showEligibility ?? false); ?>;
2086 var listId = '#' + <?php echo js_escape($list_id); ?>;
2087 $(function() {
2088 $(listId).addClass("active");
2089 if (isPost === true) {
2090 $("#eligibility").click();
2091 $("#eligibility").get(0).scrollIntoView();
2094 </script>
2095 </body>
2096 <?php $ed->dispatch(new RenderEvent($pid), RenderEvent::EVENT_RENDER_POST_PAGELOAD, 10); ?>
2097 </html>