7 * @link http://www.open-emr.org
8 * @author Kevin Yeh <kevin.y@integralemr.com>
9 * @author Brady Miller <brady.g.miller@gmail.com>
10 * @author Ranganath Pathak <pathak@scrs1.org>
11 * @author Jerry Padgett <sjpadgett@gmail.com>
12 * @copyright Copyright (c) 2016 Kevin Yeh <kevin.y@integralemr.com>
13 * @copyright Copyright (c) 2016-2019 Brady Miller <brady.g.miller@gmail.com>
14 * @copyright Copyright (c) 2019 Ranganath Pathak <pathak@scrs1.org>
15 * @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3
18 $sessionAllowWrite = true;
19 require_once(__DIR__
. '/../../globals.php');
20 require_once $GLOBALS['srcdir'] . '/ESign/Api.php';
23 use OpenEMR\Common\Acl\AclMain
;
24 use OpenEMR\Common\Csrf\CsrfUtils
;
25 use OpenEMR\Core\Header
;
26 use OpenEMR\Events\Main\Tabs\RenderEvent
;
28 // Ensure token_main matches so this script can not be run by itself
29 // If do not match, then destroy the session and go back to login screen
31 (empty($_SESSION['token_main_php'])) ||
32 (empty($_GET['token_main'])) ||
33 ($_GET['token_main'] != $_SESSION['token_main_php'])
35 // Below functions are from auth.inc, which is included in globals.php
37 authLoginScreen(false);
39 // this will not allow copy/paste of the link to this main.php page or a refresh of this main.php page
40 // (default behavior, however, this behavior can be turned off in the prevent_browser_refresh global)
41 if ($GLOBALS['prevent_browser_refresh'] > 1) {
42 unset($_SESSION['token_main_php']);
45 $esignApi = new Api();
51 <title
><?php
echo text($openemr_name); ?
></title
>
54 // This is to prevent users from losing data by refreshing or backing out of OpenEMR.
55 // (default behavior, however, this behavior can be turned off in the prevent_browser_refresh global)
56 <?php
if ($GLOBALS['prevent_browser_refresh'] > 0) { ?
>
57 window
.addEventListener('beforeunload', (event
) => {
59 event
.returnValue
= <?php
echo xlj('Recommend not leaving or refreshing or you may lose data.'); ?
>;
64 <?php
require($GLOBALS['srcdir'] . "/restoreSession.php"); ?
>
66 // Since this should be the parent window, this is to prevent calls to the
67 // window that opened this window. For example when a new window is opened
68 // from the Patient Flow Board or the Patient Finder.
72 // This flag indicates if another window or frame is trying to reload the login
73 // page to this top-level window. It is set by javascript returned by auth.inc.php
74 // and is checked by handlers of beforeunload events.
75 var timed_out
= false;
76 // some globals to access using top.variable
77 // note that 'let' or 'const' does not allow global scope here.
79 var isPortalEnabled
= "<?php echo $GLOBALS['portal_onsite_two_enable'] ?>";
80 // Set the csrf_token_js token that is used in the below js/tabs_view_model.js script
81 var csrf_token_js
= <?php
echo js_escape(CsrfUtils
::collectCsrfToken()); ?
>;
82 var userDebug
= <?php
echo js_escape($GLOBALS['user_debug']); ?
>;
83 var webroot_url
= <?php
echo js_escape($web_root); ?
>;
84 var jsLanguageDirection
= <?php
echo js_escape($_SESSION['language_direction']); ?
>;
86 // used in tabs_view_model.js.
87 jsGlobals
.enable_group_therapy
= <?php
echo js_escape($GLOBALS['enable_group_therapy']); ?
>
89 var WindowTitleAddPatient
= <?php
echo ($GLOBALS['window_title_add_patient_name'] ?
'true' : 'false' ); ?
>;
90 var WindowTitleBase
= <?php
echo js_escape($openemr_name); ?
>;
92 function goRepeaterServices() {
93 // Ensure send the skip_timeout_reset parameter to not count this as a manual entry in the
94 // timing out mechanism in OpenEMR.
96 // Send the skip_timeout_reset parameter to not count this as a manual entry in the
97 // timing out mechanism in OpenEMR. Notify App for various portal and reminder alerts.
98 // Combined portal and reminders ajax to fetch sjp 06-07-2020.
99 // Incorporated timeout mechanism in 2021
101 let request
= new FormData
;
102 request
.append("skip_timeout_reset", "1");
103 request
.append("isPortal", isPortalEnabled
);
104 request
.append("csrf_token_form", csrf_token_js
);
105 fetch(webroot_url +
"/library/ajax/dated_reminders_counter.php", {
107 credentials
: 'same-origin',
109 }).then((response
) => {
110 if (response
.status
!== 200) {
111 console
.log('Reminders start failed. Status Code: ' + response
.status
);
114 return response
.json();
116 if (data
.timeoutMessage
&& (data
.timeoutMessage
== 'timeout')) {
117 // timeout has happened, so logout
120 if (isPortalEnabled
) {
121 let mail
= data
.mailCnt
;
122 let chats
= data
.chatCnt
;
123 let audits
= data
.auditCnt
;
124 let payments
= data
.paymentCnt
;
125 let total
= data
.total
;
126 let enable
= ((1 * mail
) +
(1 * audits
)); // payments are among audits.
127 // Send portal counts to notification button model
128 // Will turn off button display if no notification!
129 app_view_model
.application_data
.user().portal(enable
);
131 app_view_model
.application_data
.user().portalAlerts(total
);
132 app_view_model
.application_data
.user().portalAudits(audits
);
133 app_view_model
.application_data
.user().portalMail(mail
);
134 app_view_model
.application_data
.user().portalChats(chats
);
135 app_view_model
.application_data
.user().portalPayments(payments
);
138 // Always send reminder count text to model
139 app_view_model
.application_data
.user().messages(data
.reminderText
);
140 }).catch(function(error
) {
141 console
.log('Request failed', error
);
144 // run background-services
145 // delay 10 seconds to prevent both utility trigger at close to same time.
146 // Both call globals so that is my concern.
147 setTimeout(function() {
149 request
= new FormData
;
150 request
.append("skip_timeout_reset", "1");
151 request
.append("ajax", "1");
152 request
.append("csrf_token_form", csrf_token_js
);
153 fetch(webroot_url +
"/library/ajax/execute_background_services.php", {
155 credentials
: 'same-origin',
157 }).then((response
) => {
158 if (response
.status
!== 200) {
159 console
.log('Background Service start failed. Status Code: ' + response
.status
);
161 }).catch(function(error
) {
162 console
.log('HTML Background Service start Request failed: ', error
);
166 // auto run this function every 60 seconds
167 var repeater
= setTimeout("goRepeaterServices()", 60000);
170 function isEncounterLocked(encounterId
) {
171 <?php
if ($esignApi->lockEncounters()) { ?
>
172 // If encounter locking is enabled, make a syncronous call (async=false) to check the
173 // DB to see if the encounter is locked.
174 // Call restore session, just in case
175 // @TODO next clean up pass, turn into await promise then modify tabs_view_model.js L-309
177 let url
= webroot_url +
"/interface/esign/index.php?module=encounter&method=esign_is_encounter_locked";
182 encounterId
: encounterId
184 success
: function(data
) {
185 encounter_locked
= data
;
190 return encounter_locked
;
192 // If encounter locking isn't enabled then always return false
198 <?php Header
::setupHeader(['knockout', 'tabs-theme', 'i18next', 'hotkeys']); ?
>
200 // set up global translations for js
201 function setupI18n(lang_id
) {
203 return fetch(<?php
echo js_escape($GLOBALS['webroot']) ?
> +
"/library/ajax/i18n_generator.php?lang_id=" +
encodeURIComponent(lang_id
) +
"&csrf_token_form=" +
encodeURIComponent(csrf_token_js
), {
204 credentials
: 'same-origin',
206 }).then((response
) => {
207 if (response
.status
!== 200) {
208 console
.log('I18n setup failed. Status Code: ' + response
.status
);
211 return response
.json();
214 setupI18n(<?php
echo js_escape($_SESSION['language_choice']); ?
>).then(translationsJson
=> {
222 translation
: translationsJson
227 console
.log(error
.message
);
231 * Assign and persist documents to portal patients
232 * @var int patientId pid
234 function assignPatientDocuments(patientId
) {
235 let url
= top
.webroot_url +
'/portal/import_template_ui.php?from_demo_pid=' +
encodeURIComponent(patientId
);
236 dlgopen(url
, 'pop-assignments', 'modal-lg', 850, '', '', {
244 <script src
="js/custom_bindings.js?v=<?php echo $v_js_includes; ?>"></script
>
245 <script src
="js/user_data_view_model.js?v=<?php echo $v_js_includes; ?>"></script
>
246 <script src
="js/patient_data_view_model.js?v=<?php echo $v_js_includes; ?>"></script
>
247 <script src
="js/therapy_group_data_view_model.js?v=<?php echo $v_js_includes; ?>"></script
>
248 <script src
="js/tabs_view_model.js?v=<?php echo $v_js_includes; ?>"></script
>
249 <script src
="js/application_view_model.js?v=<?php echo $v_js_includes; ?>"></script
>
250 <script src
="js/frame_proxies.js?v=<?php echo $v_js_includes; ?>"></script
>
251 <script src
="js/dialog_utils.js?v=<?php echo $v_js_includes; ?>"></script
>
252 <script src
="js/shortcuts.js?v=<?php echo $v_js_includes; ?>"></script
>
255 // Below code block is to prepare certain elements for deciding what links to show on the menu
257 // prepare newcrop globals that are used in creating the menu
258 if ($GLOBALS['erx_enable']) {
259 $newcrop_user_role_sql = sqlQuery("SELECT `newcrop_user_role` FROM `users` WHERE `username` = ?", array($_SESSION['authUser']));
260 $GLOBALS['newcrop_user_role'] = $newcrop_user_role_sql['newcrop_user_role'];
261 if ($GLOBALS['newcrop_user_role'] === 'erxadmin') {
262 $GLOBALS['newcrop_user_role_erxadmin'] = 1;
266 // prepare track anything to be used in creating the menu
267 $track_anything_sql = sqlQuery("SELECT `state` FROM `registry` WHERE `directory` = 'track_anything'");
268 $GLOBALS['track_anything_state'] = ($track_anything_sql['state'] ??
0);
269 // prepare Issues popup link global that is used in creating the menu
270 $GLOBALS['allow_issue_menu_link'] = ((AclMain
::aclCheckCore('encounters', 'notes', '', 'write') || AclMain
::aclCheckCore('encounters', 'notes_a', '', 'write')) &&
271 AclMain
::aclCheckCore('patients', 'med', '', 'write'));
274 <?php
require_once("templates/tabs_template.php"); ?
>
275 <?php
require_once("templates/menu_template.php"); ?
>
276 <?php
require_once("templates/patient_data_template.php"); ?
>
277 <?php
require_once("templates/therapy_group_template.php"); ?
>
278 <?php
require_once("templates/user_data_template.php"); ?
>
279 <?php
require_once("menu/menu_json.php"); ?
>
280 <?php
$userQuery = sqlQuery("select * from users where username = ?", array($_SESSION['authUser'])); ?
>
283 <?php
if (!empty($_SESSION['frame1url']) && !empty($_SESSION['frame1target'])) { ?
>
284 // Use session variables and tabStatus object to set up initial/default first tab
285 app_view_model
.application_data
.tabs
.tabsList
.push(new tabStatus(<?php
echo xlj("Loading"); ?
> +
"...", <?php
echo json_encode("../" . $_SESSION['frame1url']); ?
>, <?php
echo json_encode($_SESSION['frame1target']); ?
>, <?php
echo xlj("Loading"); ?
> +
" " +
<?php
echo json_encode($_SESSION['frame1label']); ?
>, true, true, false));
288 <?php
if (!empty($_SESSION['frame2url']) && !empty($_SESSION['frame2target'])) { ?
>
289 // Use session variables and tabStatus object to set up initial/default second tab, if none is set in globals, this tab will not be displayed initially
290 app_view_model
.application_data
.tabs
.tabsList
.push(new tabStatus(<?php
echo xlj("Loading"); ?
> +
"...", <?php
echo json_encode("../" . $_SESSION['frame2url']); ?
>, <?php
echo json_encode($_SESSION['frame2target']); ?
>, <?php
echo xlj("Loading"); ?
> +
" " +
<?php
echo json_encode($_SESSION['frame2label']); ?
>, true, false, false));
293 app_view_model
.application_data
.user(new user_data_view_model(<?php
echo json_encode($_SESSION["authUser"])
294 . ',' . json_encode($userQuery['fname'])
295 . ',' . json_encode($userQuery['lname'])
296 . ',' . json_encode($_SESSION['authProvider']); ?
>));
302 min
-height
: 100%
!important
;
303 height
: 100%
!important
;
308 <body
class="min-vw-100">
310 // fire off an event here
311 if (!empty($GLOBALS['kernel']->getEventDispatcher())) {
313 * @var \Symfony\Component\EventDispatcher\EventDispatcher
315 $dispatcher = $GLOBALS['kernel']->getEventDispatcher();
316 $dispatcher->dispatch(new RenderEvent(), RenderEvent
::EVENT_BODY_RENDER_PRE
);
319 <!-- Below iframe is to support logout
, which needs to be run in an inner iframe to work
as intended
-->
320 <iframe name
="logoutinnerframe" id
="logoutinnerframe" style
="visibility:hidden; position:absolute; left:0; top:0; height:0; width:0; border:none;" src
="about:blank"></iframe
>
321 <?php
// mdsupport - app settings
323 if (isset($_SESSION['app1'])) {
325 "SELECT title app_url FROM list_options WHERE activity=1 AND list_id=? AND option_id=?",
326 array('apps', $_SESSION['app1'])
328 if ($rs['app_url'] != "main/main_screen.php") {
329 echo '<iframe name="app1" src="../../' . attr($rs['app_url']) . '"
330 style="position: absolute; left: 0; top: 0; height: 100%; width: 100%; border: none;" />';
331 $disp_mainBox = 'style="display: none;"';
335 <div id
="mainBox" <?php
echo $disp_mainBox ?
>>
336 <nav
class="navbar navbar-expand-xl navbar-light bg-light py-0">
337 <?php
if ($GLOBALS['display_main_menu_logo'] === '1') : ?
>
338 <a
class="navbar-brand mt-2 mt-xl-0 mr-3 mr-xl-2" href
="https://www.open-emr.org" title
="OpenEMR <?php echo xla("Website
"); ?>" rel
="noopener" target
="_blank">
339 <?php
echo file_get_contents($GLOBALS['images_static_absolute'] . "/menu-logo.svg"); ?
>
342 <button
class="navbar-toggler mr-auto" type
="button" data
-toggle
="collapse" data
-target
="#mainMenu" aria
-controls
="mainMenu" aria
-expanded
="false" aria
-label
="Toggle navigation">
343 <span
class="navbar-toggler-icon"></span
>
345 <div
class="collapse navbar-collapse" id
="mainMenu" data
-bind
="template: {name: 'menu-template', data: application_data}"></div
>
346 <form name
="frm_search_globals" class="form-inline">
347 <div
class="input-group">
348 <input type
="text" id
="anySearchBox" class="form-control-sm <?php echo $any_search_class ?> form-control" name
="anySearchBox" placeholder
="<?php echo xla("Search by any demographics
") ?>" autocomplete
="off">
349 <div
class="input-group-append">
350 <button type
="button" id
="search_globals" class="btn btn-sm btn-secondary <?php echo $search_globals_class ?>" title
='<?php echo xla("Search for patient by entering whole or part of any demographics field information"); ?>' data
-bind
="event: {mousedown: viewPtFinder.bind( $data, '<?php echo xla("The search field cannot be
empty. Please enter a search term
") ?>', '<?php echo attr($search_any_type); ?>')}"><i
class="fa fa-search"> 
;</i
></button
>
354 <span id
="userData" data
-bind
="template: {name: 'user-data-template', data: application_data}"></span
>
356 <div id
="attendantData" class="body_title acck" data
-bind
="template: {name: app_view_model.attendant_template_type, data: application_data}"></div
>
357 <div
class="body_title" id
="tabs_div" data
-bind
="template: {name: 'tabs-controls', data: application_data}"></div
>
358 <div
class="mainFrames d-flex flex-row" id
="mainFrames_div">
359 <div id
="framesDisplay" data
-bind
="template: {name: 'tabs-frames', data: application_data}"></div
>
363 ko
.applyBindings(app_view_model
);
366 $
('.dropdown-toggle').dropdown();
367 $
('#patient_caret').click(function() {
368 $
('#attendantData').slideToggle();
369 $
('#patient_caret').toggleClass('fa-caret-down').toggleClass('fa-caret-up');
371 if ($
('body').css('direction') == "rtl") {
372 $
('.dropdown-menu-right').each(function() {
373 $
(this
).removeClass('dropdown-menu-right');
378 $
('#logo_menu').focus();
380 $
('#anySearchBox').keypress(function(event
) {
381 if (event
.which
=== 13 || event
.keyCode
=== 13) {
382 event
.preventDefault();
383 $
('#search_globals').mousedown();
386 document
.addEventListener('touchstart', {}); //specifically added for iOS devices, especially in iframes
388 goRepeaterServices();
392 // fire off an event here
393 if (!empty($GLOBALS['kernel']->getEventDispatcher())) {
395 * @var \Symfony\Component\EventDispatcher\EventDispatcher
397 $dispatcher = $GLOBALS['kernel']->getEventDispatcher();
398 $dispatcher->dispatch(new RenderEvent(), RenderEvent
::EVENT_BODY_RENDER_POST
);